fix(cli): make command registration snapshot-safe

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 06:45:31 +00:00
parent 88ec9264a9
commit b8f3c9698a
2 changed files with 36 additions and 6 deletions

View file

@ -105,15 +105,23 @@ func loadLocaleSources(sources ...LocaleSource) {
func RegisteredLocales() []fs.FS {
registeredCommandsMu.Lock()
defer registeredCommandsMu.Unlock()
return registeredLocales
if len(registeredLocales) == 0 {
return nil
}
out := make([]fs.FS, len(registeredLocales))
copy(out, registeredLocales)
return out
}
// RegisteredCommands returns an iterator over the registered command functions.
func RegisteredCommands() iter.Seq[CommandRegistration] {
return func(yield func(CommandRegistration) bool) {
registeredCommandsMu.Lock()
defer registeredCommandsMu.Unlock()
for _, fn := range registeredCommands {
snapshot := make([]CommandRegistration, len(registeredCommands))
copy(snapshot, registeredCommands)
registeredCommandsMu.Unlock()
for _, fn := range snapshot {
if !yield(fn) {
return
}
@ -125,10 +133,12 @@ func RegisteredCommands() iter.Seq[CommandRegistration] {
// Called by Init() after creating the root command.
func attachRegisteredCommands(root *cobra.Command) {
registeredCommandsMu.Lock()
defer registeredCommandsMu.Unlock()
snapshot := make([]CommandRegistration, len(registeredCommands))
copy(snapshot, registeredCommands)
commandsAttached = true
registeredCommandsMu.Unlock()
for _, fn := range registeredCommands {
for _, fn := range snapshot {
fn(root)
}
commandsAttached = true
}

View file

@ -148,6 +148,26 @@ func TestRegisterCommands_Bad(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "late", cmd.Use)
})
t.Run("nested registration during startup does not deadlock", func(t *testing.T) {
resetGlobals(t)
RegisterCommands(func(root *cobra.Command) {
root.AddCommand(&cobra.Command{Use: "outer", Short: "Outer"})
RegisterCommands(func(root *cobra.Command) {
root.AddCommand(&cobra.Command{Use: "inner", Short: "Inner"})
})
})
err := Init(Options{AppName: "test"})
require.NoError(t, err)
for _, name := range []string{"outer", "inner"} {
cmd, _, err := RootCmd().Find([]string{name})
require.NoError(t, err)
assert.Equal(t, name, cmd.Use)
}
})
}
// TestLocaleLoading_Good verifies locale files become available to the active i18n service.