refactor(cli): register commands through Core framework lifecycle
Replace the RegisterCommands/attachRegisteredCommands side-channel with WithCommands(), which wraps command registration functions as framework services. Commands now participate in the Core lifecycle via OnStartup, receiving the root cobra.Command through Core.App. Main() accepts variadic framework.Option so binaries pass their commands explicitly — no init(), no blank imports, no global state. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
8e7fb0e5a3
commit
2a90ae65b7
4 changed files with 45 additions and 58 deletions
|
|
@ -13,9 +13,3 @@
|
|||
//
|
||||
// Sets MACOSX_DEPLOYMENT_TARGET to suppress linker warnings on macOS.
|
||||
package gocmd
|
||||
|
||||
import "forge.lthn.ai/core/go/pkg/cli"
|
||||
|
||||
func init() {
|
||||
cli.RegisterCommands(AddGoCommands)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,16 @@ func SemVer() string {
|
|||
}
|
||||
|
||||
// Main initialises and runs the CLI application.
|
||||
// This is the main entry point for the CLI.
|
||||
// Pass command services via WithCommands to register CLI commands
|
||||
// through the Core framework lifecycle.
|
||||
//
|
||||
// cli.Main(
|
||||
// cli.WithCommands("config", config.AddConfigCommands),
|
||||
// cli.WithCommands("doctor", doctor.AddDoctorCommands),
|
||||
// )
|
||||
//
|
||||
// Exits with code 1 on error or panic.
|
||||
func Main() {
|
||||
func Main(commands ...framework.Option) {
|
||||
// Recovery from panics
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
|
@ -60,17 +67,21 @@ func Main() {
|
|||
}
|
||||
}()
|
||||
|
||||
// Core services load first, then command services
|
||||
services := []framework.Option{
|
||||
framework.WithName("i18n", NewI18nService(I18nOptions{})),
|
||||
framework.WithName("log", NewLogService(log.Options{
|
||||
Level: log.LevelInfo,
|
||||
})),
|
||||
framework.WithName("workspace", workspace.New),
|
||||
}
|
||||
services = append(services, commands...)
|
||||
|
||||
// Initialise CLI runtime with services
|
||||
if err := Init(Options{
|
||||
AppName: AppName,
|
||||
Version: SemVer(),
|
||||
Services: []framework.Option{
|
||||
framework.WithName("i18n", NewI18nService(I18nOptions{})),
|
||||
framework.WithName("log", NewLogService(log.Options{
|
||||
Level: log.LevelInfo,
|
||||
})),
|
||||
framework.WithName("workspace", workspace.New),
|
||||
},
|
||||
AppName: AppName,
|
||||
Version: SemVer(),
|
||||
Services: services,
|
||||
}); err != nil {
|
||||
Error(err.Error())
|
||||
os.Exit(1)
|
||||
|
|
|
|||
|
|
@ -2,49 +2,34 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"context"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/framework"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CommandRegistration is a function that adds commands to the root.
|
||||
type CommandRegistration func(root *cobra.Command)
|
||||
|
||||
var (
|
||||
registeredCommands []CommandRegistration
|
||||
registeredCommandsMu sync.Mutex
|
||||
commandsAttached bool
|
||||
)
|
||||
|
||||
// RegisterCommands registers a function that adds commands to the CLI.
|
||||
// Call this in your package's init() to register commands.
|
||||
// WithCommands creates a framework Option that registers a command group.
|
||||
// The register function receives the root command during service startup,
|
||||
// allowing commands to participate in the Core lifecycle.
|
||||
//
|
||||
// func init() {
|
||||
// cli.RegisterCommands(AddCommands)
|
||||
// }
|
||||
//
|
||||
// func AddCommands(root *cobra.Command) {
|
||||
// root.AddCommand(myCmd)
|
||||
// }
|
||||
func RegisterCommands(fn CommandRegistration) {
|
||||
registeredCommandsMu.Lock()
|
||||
defer registeredCommandsMu.Unlock()
|
||||
registeredCommands = append(registeredCommands, fn)
|
||||
|
||||
// If commands already attached (CLI already running), attach immediately
|
||||
if commandsAttached && instance != nil && instance.root != nil {
|
||||
fn(instance.root)
|
||||
}
|
||||
// cli.Main(
|
||||
// cli.WithCommands("config", config.AddConfigCommands),
|
||||
// cli.WithCommands("doctor", doctor.AddDoctorCommands),
|
||||
// )
|
||||
func WithCommands(name string, register func(root *Command)) framework.Option {
|
||||
return framework.WithName("cmd."+name, func(c *framework.Core) (any, error) {
|
||||
return &commandService{core: c, register: register}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// attachRegisteredCommands calls all registered command functions.
|
||||
// Called by Init() after creating the root command.
|
||||
func attachRegisteredCommands(root *cobra.Command) {
|
||||
registeredCommandsMu.Lock()
|
||||
defer registeredCommandsMu.Unlock()
|
||||
|
||||
for _, fn := range registeredCommands {
|
||||
fn(root)
|
||||
}
|
||||
commandsAttached = true
|
||||
type commandService struct {
|
||||
core *framework.Core
|
||||
register func(root *Command)
|
||||
}
|
||||
|
||||
func (s *commandService) OnStartup(_ context.Context) error {
|
||||
if root, ok := s.core.App.(*cobra.Command); ok {
|
||||
s.register(root)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,6 @@ func Init(opts Options) error {
|
|||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
// Attach all registered commands
|
||||
attachRegisteredCommands(rootCmd)
|
||||
|
||||
// Build signal service options
|
||||
var signalOpts []SignalOption
|
||||
if opts.OnReload != nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue