refactor(cli): clean DX with direct function calls
- cli.Success(), cli.Error(), etc. now print directly
- String-returning versions renamed to cli.FmtSuccess(), etc.
- Removes App() from common usage path
- Usage: cli.Success("done") instead of fmt.Println(cli.Success("done"))
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
23d399407c
commit
22aa1df30a
3 changed files with 75 additions and 101 deletions
|
|
@ -95,12 +95,12 @@ func runDoctor(verbose bool) error {
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Println(cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed})))
|
cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))
|
||||||
fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing"))
|
fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing"))
|
||||||
printInstallInstructions()
|
printInstallInstructions()
|
||||||
return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed}))
|
return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(cli.Success(i18n.T("cmd.doctor.ready")))
|
cli.Success(i18n.T("cmd.doctor.ready"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
// Package cli provides the CLI runtime and utilities.
|
// Package cli provides the CLI runtime and utilities.
|
||||||
//
|
//
|
||||||
// The CLI uses the Core framework for its own runtime, providing:
|
// The CLI uses the Core framework for its own runtime. Usage is simple:
|
||||||
// - Global singleton access via cli.App()
|
//
|
||||||
// - Output service for styled terminal printing
|
// cli.Init(cli.Options{AppName: "core"})
|
||||||
// - Signal handling for graceful shutdown
|
// defer cli.Shutdown()
|
||||||
// - Worker bundle spawning for commands
|
//
|
||||||
|
// cli.Success("Done!")
|
||||||
|
// cli.Error("Failed")
|
||||||
|
// if cli.Confirm("Proceed?") { ... }
|
||||||
|
//
|
||||||
|
// // When you need the Core instance
|
||||||
|
// c := cli.Core()
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -19,39 +25,32 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
instance *Runtime
|
instance *runtime
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime is the CLI's Core runtime.
|
// runtime is the CLI's internal Core runtime.
|
||||||
type Runtime struct {
|
type runtime struct {
|
||||||
Core *framework.Core
|
core *framework.Core
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuntimeOptions configures the CLI runtime.
|
// Options configures the CLI runtime.
|
||||||
type RuntimeOptions struct {
|
type Options struct {
|
||||||
// AppName is the CLI application name (used in output)
|
|
||||||
AppName string
|
AppName string
|
||||||
// Version is the CLI version string
|
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialises the global CLI runtime.
|
// Init initialises the global CLI runtime.
|
||||||
// Call this once at startup (typically in main.go).
|
// Call this once at startup (typically in main.go).
|
||||||
func Init(opts RuntimeOptions) error {
|
func Init(opts Options) error {
|
||||||
var initErr error
|
var initErr error
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
core, err := framework.New(
|
c, err := framework.New(
|
||||||
framework.WithService(NewOutputService(OutputServiceOptions{
|
framework.WithName("signal", newSignalService(cancel)),
|
||||||
AppName: opts.AppName,
|
|
||||||
})),
|
|
||||||
framework.WithService(NewSignalService(SignalServiceOptions{
|
|
||||||
Cancel: cancel,
|
|
||||||
})),
|
|
||||||
framework.WithServiceLock(),
|
framework.WithServiceLock(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -60,14 +59,13 @@ func Init(opts RuntimeOptions) error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = &Runtime{
|
instance = &runtime{
|
||||||
Core: core,
|
core: c,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start services
|
if err := c.ServiceStartup(ctx, nil); err != nil {
|
||||||
if err := core.ServiceStartup(ctx, nil); err != nil {
|
|
||||||
initErr = err
|
initErr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -75,114 +73,91 @@ func Init(opts RuntimeOptions) error {
|
||||||
return initErr
|
return initErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// App returns the global CLI runtime.
|
func mustInit() {
|
||||||
// Panics if Init() hasn't been called.
|
|
||||||
func App() *Runtime {
|
|
||||||
if instance == nil {
|
if instance == nil {
|
||||||
panic("cli.App() called before cli.Init()")
|
panic("cli not initialised - call cli.Init() first")
|
||||||
}
|
}
|
||||||
return instance
|
}
|
||||||
|
|
||||||
|
// --- Core Access ---
|
||||||
|
|
||||||
|
// Core returns the CLI's framework Core instance.
|
||||||
|
func Core() *framework.Core {
|
||||||
|
mustInit()
|
||||||
|
return instance.core
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context returns the CLI's root context.
|
// Context returns the CLI's root context.
|
||||||
// This context is cancelled on shutdown signals.
|
// Cancelled on SIGINT/SIGTERM.
|
||||||
func (r *Runtime) Context() context.Context {
|
func Context() context.Context {
|
||||||
return r.ctx
|
mustInit()
|
||||||
|
return instance.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully shuts down the CLI runtime.
|
// Shutdown gracefully shuts down the CLI.
|
||||||
func (r *Runtime) Shutdown() {
|
func Shutdown() {
|
||||||
r.cancel()
|
if instance == nil {
|
||||||
r.Core.ServiceShutdown(r.ctx)
|
return
|
||||||
}
|
|
||||||
|
|
||||||
// Output returns the output service for styled printing.
|
|
||||||
func (r *Runtime) Output() *OutputService {
|
|
||||||
return framework.MustServiceFor[*OutputService](r.Core, "output")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Output Service ---
|
|
||||||
|
|
||||||
// OutputServiceOptions configures the output service.
|
|
||||||
type OutputServiceOptions struct {
|
|
||||||
AppName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutputService provides styled terminal output.
|
|
||||||
type OutputService struct {
|
|
||||||
*framework.ServiceRuntime[OutputServiceOptions]
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOutputService creates an output service factory.
|
|
||||||
func NewOutputService(opts OutputServiceOptions) func(*framework.Core) (any, error) {
|
|
||||||
return func(c *framework.Core) (any, error) {
|
|
||||||
return &OutputService{
|
|
||||||
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
instance.cancel()
|
||||||
|
instance.core.ServiceShutdown(instance.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Output Functions ---
|
||||||
|
|
||||||
// Success prints a success message with checkmark.
|
// Success prints a success message with checkmark.
|
||||||
func (s *OutputService) Success(msg string) {
|
func Success(msg string) {
|
||||||
fmt.Println(SuccessStyle.Render(SymbolCheck + " " + msg))
|
fmt.Println(SuccessStyle.Render(SymbolCheck + " " + msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error prints an error message with cross.
|
// Error prints an error message with cross.
|
||||||
func (s *OutputService) Error(msg string) {
|
func Error(msg string) {
|
||||||
fmt.Println(ErrorStyle.Render(SymbolCross + " " + msg))
|
fmt.Println(ErrorStyle.Render(SymbolCross + " " + msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning prints a warning message.
|
// Warning prints a warning message.
|
||||||
func (s *OutputService) Warning(msg string) {
|
func Warning(msg string) {
|
||||||
fmt.Println(WarningStyle.Render(SymbolWarning + " " + msg))
|
fmt.Println(WarningStyle.Render(SymbolWarning + " " + msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info prints an info message.
|
// Info prints an info message.
|
||||||
func (s *OutputService) Info(msg string) {
|
func Info(msg string) {
|
||||||
fmt.Println(InfoStyle.Render(SymbolInfo + " " + msg))
|
fmt.Println(InfoStyle.Render(SymbolInfo + " " + msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title prints a title/header.
|
// Title prints a title/header.
|
||||||
func (s *OutputService) Title(msg string) {
|
func Title(msg string) {
|
||||||
fmt.Println(TitleStyle.Render(msg))
|
fmt.Println(TitleStyle.Render(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dim prints dimmed/subtle text.
|
// Dim prints dimmed/subtle text.
|
||||||
func (s *OutputService) Dim(msg string) {
|
func Dim(msg string) {
|
||||||
fmt.Println(DimStyle.Render(msg))
|
fmt.Println(DimStyle.Render(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Signal Service ---
|
// --- Signal Service (internal) ---
|
||||||
|
|
||||||
// SignalServiceOptions configures the signal service.
|
type signalService struct {
|
||||||
type SignalServiceOptions struct {
|
cancel context.CancelFunc
|
||||||
Cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignalService handles OS signals for graceful shutdown.
|
|
||||||
type SignalService struct {
|
|
||||||
*framework.ServiceRuntime[SignalServiceOptions]
|
|
||||||
sigChan chan os.Signal
|
sigChan chan os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignalService creates a signal service factory.
|
func newSignalService(cancel context.CancelFunc) func(*framework.Core) (any, error) {
|
||||||
func NewSignalService(opts SignalServiceOptions) func(*framework.Core) (any, error) {
|
|
||||||
return func(c *framework.Core) (any, error) {
|
return func(c *framework.Core) (any, error) {
|
||||||
return &SignalService{
|
return &signalService{
|
||||||
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
cancel: cancel,
|
||||||
sigChan: make(chan os.Signal, 1),
|
sigChan: make(chan os.Signal, 1),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStartup starts listening for signals.
|
func (s *signalService) OnStartup(ctx context.Context) error {
|
||||||
func (s *SignalService) OnStartup(ctx context.Context) error {
|
|
||||||
signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-s.sigChan:
|
case <-s.sigChan:
|
||||||
s.Opts().Cancel()
|
s.cancel()
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -190,8 +165,7 @@ func (s *SignalService) OnStartup(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnShutdown stops listening for signals.
|
func (s *signalService) OnShutdown(ctx context.Context) error {
|
||||||
func (s *SignalService) OnShutdown(ctx context.Context) error {
|
|
||||||
signal.Stop(s.sigChan)
|
signal.Stop(s.sigChan)
|
||||||
close(s.sigChan)
|
close(s.sigChan)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -327,23 +327,23 @@ var (
|
||||||
// Helper Functions
|
// Helper Functions
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Success returns a styled success message with checkmark.
|
// FmtSuccess returns a styled success message with checkmark.
|
||||||
func Success(msg string) string {
|
func FmtSuccess(msg string) string {
|
||||||
return fmt.Sprintf("%s %s", SuccessStyle.Render(SymbolCheck), msg)
|
return fmt.Sprintf("%s %s", SuccessStyle.Render(SymbolCheck), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns a styled error message with cross.
|
// FmtError returns a styled error message with cross.
|
||||||
func Error(msg string) string {
|
func FmtError(msg string) string {
|
||||||
return fmt.Sprintf("%s %s", ErrorStyle.Render(SymbolCross), msg)
|
return fmt.Sprintf("%s %s", ErrorStyle.Render(SymbolCross), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning returns a styled warning message with warning symbol.
|
// FmtWarning returns a styled warning message with warning symbol.
|
||||||
func Warning(msg string) string {
|
func FmtWarning(msg string) string {
|
||||||
return fmt.Sprintf("%s %s", WarningStyle.Render(SymbolWarning), msg)
|
return fmt.Sprintf("%s %s", WarningStyle.Render(SymbolWarning), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info returns a styled info message with info symbol.
|
// FmtInfo returns a styled info message with info symbol.
|
||||||
func Info(msg string) string {
|
func FmtInfo(msg string) string {
|
||||||
return fmt.Sprintf("%s %s", InfoStyle.Render(SymbolInfo), msg)
|
return fmt.Sprintf("%s %s", InfoStyle.Render(SymbolInfo), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,13 +464,13 @@ func Header(title string, withSeparator bool) string {
|
||||||
return fmt.Sprintf("\n%s", HeaderStyle.Render(title))
|
return fmt.Sprintf("\n%s", HeaderStyle.Render(title))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title returns a styled command/section title.
|
// FmtTitle returns a styled command/section title.
|
||||||
func Title(text string) string {
|
func FmtTitle(text string) string {
|
||||||
return TitleStyle.Render(text)
|
return TitleStyle.Render(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dim returns dimmed text.
|
// FmtDim returns dimmed text.
|
||||||
func Dim(text string) string {
|
func FmtDim(text string) string {
|
||||||
return DimStyle.Render(text)
|
return DimStyle.Render(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue