Add Initialiser
Practical, step-by-step instructions for creating and registering a custom Initialiser.
Important
Initialisers are for configuration, not logic.
The goal of an Initialiser is to ensure the necessary values (API keys, paths, preferences) are present in the config.yaml. The actual functional logic of your feature should reside in your cobra.Command and its associated business logic.
Step 1: Implement the Initialiser Interface¶
Create a new package or add a file to your feature package that implements setup.Initialiser.
package myfeature
import (
"context"
"gitlab.com/phpboyscout/go/config"
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
"gitlab.com/phpboyscout/go-tool-base/pkg/setup"
)
type MyInitialiser struct{}
func (m *MyInitialiser) Name() string { return "My Feature" }
func (m *MyInitialiser) IsConfigured(cfg config.Reader) bool {
return cfg.GetString("myfeature.key") != ""
}
func (m *MyInitialiser) Configure(ctx context.Context, props *props.Props, cfg setup.Editor) error {
props.Logger.Info("Configuring My Feature...")
// Perform interactive work here: build a huh form and run it with
// setup.RunForm(ctx, props, form), which honours --accessible and refuses
// cleanly when nobody is at a terminal. Writes go through the editor,
// which applies them to the user's config file in place.
return cfg.Set("myfeature.key", "some-value")
}
Step 2: Set up Flag Registration (Optional)¶
If your initialiser should be skippable via a CLI flag (e.g., --skip-myfeature), write a flag provider that binds it on the init command. The flag belongs to that command, so don't bind it to a package-level variable: your provider reads it from the flag set init hands it (Step 3).
func registerFlags(cmd *cobra.Command) {
cmd.Flags().Bool("skip-myfeature", false, "Skip setting up my feature")
}
Step 3: Register your Initialiser¶
Use the setup.Register function in your package's init() block. This ensures that when your package is imported, the initialiser becomes available to the framework.
init only applies the contributions of enabled features, so register under a feature your tool enables. A framework feature registers under its own ID; a command in your own tool registers under props.InitCmd, which is what gtb generate command --with-initializer emits.
If your feature ships embedded config, an assets/config.yaml defaults
document or an assets/init/config.yaml template that init should write into
the user's file, announce the bundle in the same init() with
setup.RegisterAssets(props.FeatureID("myfeature"), "myfeature", &assets).
The root command applies the bundles of enabled features during construction,
so a disabled feature's config never leaks in.
package myfeature
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
"gitlab.com/phpboyscout/go-tool-base/pkg/setup"
)
func init() {
setup.Register(props.InitCmd,
[]setup.InitialiserProvider{
func(p *props.Props, flags *pflag.FlagSet) setup.Initialiser {
if setup.FlagSkips(flags, "skip-myfeature")["skip-myfeature"] {
return nil // a nil initialiser is a skipped one
}
return &MyInitialiser{}
},
},
[]setup.SubcommandProvider{
func(p *props.Props) []*cobra.Command {
return []*cobra.Command{
{
Use: "myfeature",
Short: "Force reconfigure my feature",
Run: func(cmd *cobra.Command, args []string) {
// Logic to run configuration directly
},
},
}
},
},
[]setup.FeatureFlag{registerFlags},
)
}
Step 4: Import your setup package¶
Because Initialisers use package-level init() functions for registration, you must ensure your setup package is imported somewhere in your dependency graph.
The best practice is to add a blank import in the package where your feature's functional CLI logic is registered. This ensures that whenever your command is included in a tool, its configuration step is automatically registered too.
package command
import (
// Blank import triggers the setup package's init() registration
_ "gitlab.com/phpboyscout/go-tool-base/pkg/setup/myfeature"
)
func NewCmdMyFeature(p *props.Props) *setup.Command {
// ...
}
Best Practices¶
- Check Env Vars: In your
IsConfiguredorConfiguremethods, check for environment variable overrides. If a value is provided via ENV, skip the interactive prompt. - Modular Assets: Values that must always apply go in
assets/config.yamlwithin your package's embedded filesystem; a starting pointinitwrites into the user's file goes inassets/init/config.yaml. Register the bundle as shown in Step 3. - Standalone Commands: If users might want to re-configure only your feature later, register a SubcommandProvider to add a command like
init myfeature.
Tip
Look at the AI Initialiser (pkg/setup/ai) or the Forge Initialiser (pkg/setup/forge) for comprehensive real-world examples.