Migrating from v0.4 to v0.5¶
v0.5 introduces a typed command composition model that replaces the previous
setup.AddCommandWithMiddleware(parent, child, feature) wiring with a
composed *setup.Command type that carries its own feature key and exposes
a Register method. Middleware is wired exactly once, at the point a parent
attaches a child β no more recursive re-wrapping, and no more bogus
props.<Name>Cmd references for user-defined commands.
The change is breaking for any package that returns *cobra.Command from a
NewCmd<Name> constructor or that passes such a value to
pkg/cmd/root.NewCmdRoot / Execute. All affected source files in a
GTB-scaffolded project are regenerated by gtb regenerate β running the
regenerator is the entire migration for end users. The notes below document
what changes for hand-written code that calls into the library directly.
Breaking Changes¶
NewCmd<Name> and NewCmdRoot return *setup.Command¶
Packages affected: pkg/cmd/root, every built-in pkg/cmd/<name>, and
every generated pkg/cmd/<name> in scaffolded projects.
Before:
import (
"github.com/spf13/cobra"
"gitlab.com/phpboyscout/go-tool-base/pkg/cmd/root"
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
"gitlab.com/phpboyscout/go-tool-base/pkg/setup"
)
func NewCmdServe(p *props.Props) *cobra.Command {
cmd := &cobra.Command{Use: "serve", RunE: runServe}
return cmd
}
// in root wiring
rootCmd := root.NewCmdRoot(p)
setup.AddCommandWithMiddleware(rootCmd, serve.NewCmdServe(p), props.ServeCmd)
root.Execute(rootCmd, p)
After:
func NewCmdServe(p *props.Props) *setup.Command {
cmd := setup.Wrap("serve", &cobra.Command{Use: "serve", RunE: runServe})
return cmd
}
// in root wiring β children attach in the variadic constructor
rootCmd := root.NewCmdRoot(p, serve.NewCmdServe(p))
root.Execute(rootCmd, p)
Migration:
- Run
gtb regeneratein a scaffolded project β the generator emits the new shape automatically. - For hand-written packages built on top of
pkg/cmd/root:- Change the return type of
NewCmd<Name>to*setup.Command. - Construct
cmdviasetup.Wrap("<feature-key>", &cobra.Command{...})socmdis the composed type from the start. Existingcmd.Flags().β¦andcmd.MarkFlagsβ¦calls keep working β they flow through the embedded*cobra.Command. - Pass children to the variadic
root.NewCmdRoot(p, ...)or toparent.Register(child). Both wire global and feature-specific middleware in a single pass.
- Change the return type of
- Replace any direct callers that expected a
*cobra.Commandwith.Command(the embedded field):myCmd.Command.SilenceErrors = true.
setup.AddCommandWithMiddleware is deprecated¶
Package: pkg/setup
AddCommandWithMiddleware(parent, child, feature *cobra.Command) and
ApplyMiddlewareRecursively(cmd, feature) are kept as // Deprecated:
shims for v0.5 to give downstream callers a window to migrate. The shim
no longer recurses into descendants β each command in the tree wraps
itself with its own feature when its parent registers it. The recursive
re-wrap-with-parent's-feature behaviour was always semantically wrong
(it imposed the parent's middleware key on children that already had
their own).
Removal planned: v1.0.
Migration: Replace each call site with parent.Register(child) β see
the example above. The child's NewCmd<Name> is now responsible for its
own feature key via setup.Wrap("<name>", cmd).
New Features¶
setup.Command, setup.Wrap, and Command.Register¶
A composed Command type wraps *cobra.Command with its own FeatureCmd
key. setup.Wrap(feature, cmd) produces one; parent.Register(child...)
attaches one or more children and wires global plus feature-specific
middleware exactly once at attach time.
parent := setup.Wrap("parent", &cobra.Command{Use: "parent"})
parent.Register(
childA.NewCmdChildA(p), // returns *setup.Command
childB.NewCmdChildB(p),
)
Each child carries its own feature, so feature-specific middleware
(registered via setup.RegisterMiddleware(feature, β¦)) applies to that
child without affecting siblings. Global middleware (registered via
RegisterGlobalMiddleware) always wraps every child.
See pkg/setup for the full API.