feat(cmd): wire root command lifecycle

Amolith and Crush created

Root command bootstraps environment for all subcommands.

- Add PersistentPreRunE to open environment
- Add PersistentPostRunE to close environment
- Register all top-level commands
- Set SilenceUsage to avoid double error output

Co-authored-by: Crush <crush@charm.land>

Change summary

cmd/root.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)

Detailed changes

cmd/root.go 🔗

@@ -5,8 +5,12 @@
 package cmd
 
 import (
+	"errors"
+
 	"git.secluded.site/np/cmd/g"
 	"git.secluded.site/np/cmd/t"
+	"git.secluded.site/np/internal/cli"
+	"git.secluded.site/np/internal/db"
 	"github.com/spf13/cobra"
 )
 
@@ -21,6 +25,53 @@ func RootCmd() *cobra.Command {
 }
 
 func init() {
+	rootCmd.SilenceUsage = true
+	rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
+		if err := openEnvironment(); err != nil {
+			return err
+		}
+		cmd.SetContext(cli.WithEnvironment(cmd.Context(), environment))
+		return nil
+	}
+	rootCmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) error {
+		return closeEnvironment()
+	}
+
+	rootCmd.AddCommand(sCmd)
+	rootCmd.AddCommand(aCmd)
+	rootCmd.AddCommand(pCmd)
+	rootCmd.AddCommand(rCmd)
+	rootCmd.AddCommand(mCmd)
 	rootCmd.AddCommand(g.GCmd)
 	rootCmd.AddCommand(t.TCmd)
 }
+
+var environment *cli.Environment
+
+func openEnvironment() error {
+	if environment != nil {
+		return nil
+	}
+	env, err := cli.OpenEnvironment(db.Options{}, nil)
+	if err != nil {
+		return err
+	}
+	environment = env
+	return nil
+}
+
+func closeEnvironment() error {
+	if environment == nil {
+		return nil
+	}
+	err := environment.Close()
+	environment = nil
+	return err
+}
+
+func requireEnvironment() (*cli.Environment, error) {
+	if environment == nil {
+		return nil, errors.New("cli: environment not initialised")
+	}
+	return environment, nil
+}