Skip to content

Commit d7db764

Browse files
authored
fix: convert global command variables to local instances (#566)
1 parent 5472be9 commit d7db764

File tree

13 files changed

+328
-317
lines changed

13 files changed

+328
-317
lines changed

internal/cmd/backup.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,30 @@ func withErrorHandling(f cobraRunEFunc) cobraRunEFunc {
4949
}
5050
}
5151

52-
var (
53-
backupCmd = &cobra.Command{
52+
func registerBackupCmd(rootCmd *cobra.Command) {
53+
backupCmd := &cobra.Command{
5454
Use: "backup <filename>",
5555
Short: "Create, restore, and inspect permissions system backups",
5656
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
5757
// Create used to be on the root, so add it here for back-compat.
5858
RunE: withErrorHandling(backupCreateCmdFunc),
5959
}
6060

61-
backupCreateCmd = &cobra.Command{
61+
backupCreateCmd := &cobra.Command{
6262
Use: "create <filename>",
6363
Short: "Backup a permission system to a file",
6464
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
6565
RunE: withErrorHandling(backupCreateCmdFunc),
6666
}
6767

68-
backupRestoreCmd = &cobra.Command{
68+
backupRestoreCmd := &cobra.Command{
6969
Use: "restore <filename>",
7070
Short: "Restore a permission system from a file",
7171
Args: commands.ValidationWrapper(commands.StdinOrExactArgs(1)),
7272
RunE: backupRestoreCmdFunc,
7373
}
7474

75-
backupParseSchemaCmd = &cobra.Command{
75+
backupParseSchemaCmd := &cobra.Command{
7676
Use: "parse-schema <filename>",
7777
Short: "Extract the schema from a backup file",
7878
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
@@ -81,7 +81,7 @@ var (
8181
},
8282
}
8383

84-
backupParseRevisionCmd = &cobra.Command{
84+
backupParseRevisionCmd := &cobra.Command{
8585
Use: "parse-revision <filename>",
8686
Short: "Extract the revision from a backup file",
8787
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
@@ -90,7 +90,7 @@ var (
9090
},
9191
}
9292

93-
backupParseRelsCmd = &cobra.Command{
93+
backupParseRelsCmd := &cobra.Command{
9494
Use: "parse-relationships <filename>",
9595
Short: "Extract the relationships from a backup file",
9696
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
@@ -99,17 +99,15 @@ var (
9999
},
100100
}
101101

102-
backupRedactCmd = &cobra.Command{
102+
backupRedactCmd := &cobra.Command{
103103
Use: "redact <filename>",
104104
Short: "Redact a backup file to remove sensitive information",
105105
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
106106
RunE: func(cmd *cobra.Command, args []string) error {
107107
return backupRedactCmdFunc(cmd, args)
108108
},
109109
}
110-
)
111110

112-
func registerBackupCmd(rootCmd *cobra.Command) {
113111
rootCmd.AddCommand(backupCmd)
114112
registerBackupCreateFlags(backupCmd)
115113

internal/cmd/cmd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ zed permission check --explain document:firstdoc writer user:emilia
116116
registerImportCmd(rootCmd)
117117
registerValidateCmd(rootCmd)
118118
registerBackupCmd(rootCmd)
119-
registerPreviewCmd(rootCmd)
120119
registerMCPCmd(rootCmd)
121120

122121
// Register shared commands.
@@ -128,7 +127,8 @@ zed permission check --explain document:firstdoc writer user:emilia
128127
commands.RegisterWatchRelationshipCmd(relCmd)
129128

130129
schemaCmd := commands.RegisterSchemaCmd(rootCmd)
131-
registerAdditionalSchemaCmds(schemaCmd)
130+
schemaCompileCmd := registerAdditionalSchemaCmds(schemaCmd)
131+
registerPreviewCmd(rootCmd, schemaCompileCmd)
132132

133133
return rootCmd
134134
}

internal/cmd/cmd_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ func TestCommandOutput(t *testing.T) {
5858
},
5959
}
6060

61-
zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))
62-
63-
rootCmd := InitialiseRootCmd(zl)
6461
for _, tt := range cases {
6562
t.Run(tt.name, func(t *testing.T) {
63+
zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))
64+
rootCmd := InitialiseRootCmd(zl)
65+
6666
var flagErrorCalled bool
6767
testFlagError := func(cmd *cobra.Command, err error) error {
6868
require.ErrorContains(t, err, tt.flagErrorContains)
@@ -86,6 +86,26 @@ func TestCommandOutput(t *testing.T) {
8686
}
8787
}
8888

89+
// TestMultipleInitialiseRootCmd is a regression test to ensure that calling
90+
// InitialiseRootCmd multiple times doesn't panic due to flag redefinition.
91+
// This fixes issue #556.
92+
func TestMultipleInitialiseRootCmd(t *testing.T) {
93+
zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))
94+
95+
// Call InitialiseRootCmd multiple times to simulate what happens
96+
// when tests run with -count=10
97+
for i := 0; i < 10; i++ {
98+
rootCmd := InitialiseRootCmd(zl)
99+
require.NotNil(t, rootCmd)
100+
101+
// Execute the command with invalid args to trigger the command tree
102+
// This ensures all commands and flags are properly initialized
103+
os.Args = []string{"zed", "version", "--invalid-flag"}
104+
err := rootCmd.ExecuteContext(t.Context())
105+
require.Error(t, err) // We expect an error due to the invalid flag
106+
}
107+
}
108+
89109
func setupOutputForTest(t *testing.T, testFlagError func(cmd *cobra.Command, err error) error, args ...string) string {
90110
t.Helper()
91111

internal/cmd/context.go

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,56 +16,54 @@ import (
1616
)
1717

1818
func registerContextCmd(rootCmd *cobra.Command) {
19-
rootCmd.AddCommand(contextCmd)
19+
contextCmd := &cobra.Command{
20+
Use: "context <subcommand>",
21+
Short: "Manage configurations for connecting to SpiceDB deployments",
22+
Aliases: []string{"ctx"},
23+
}
24+
25+
contextListCmd := &cobra.Command{
26+
Use: "list",
27+
Short: "Lists all available contexts",
28+
Aliases: []string{"ls"},
29+
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
30+
ValidArgsFunction: cobra.NoFileCompletions,
31+
RunE: contextListCmdFunc,
32+
}
33+
34+
contextSetCmd := &cobra.Command{
35+
Use: "set <name> <endpoint> <api-token>",
36+
Short: "Creates or overwrite a context",
37+
Args: commands.ValidationWrapper(cobra.ExactArgs(3)),
38+
ValidArgsFunction: cobra.NoFileCompletions,
39+
RunE: contextSetCmdFunc,
40+
}
2041

42+
contextRemoveCmd := &cobra.Command{
43+
Use: "remove <system>",
44+
Short: "Removes a context",
45+
Aliases: []string{"rm"},
46+
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
47+
ValidArgsFunction: ContextGet,
48+
RunE: contextRemoveCmdFunc,
49+
}
50+
51+
contextUseCmd := &cobra.Command{
52+
Use: "use <system>",
53+
Short: "Sets a context as the current context",
54+
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
55+
ValidArgsFunction: ContextGet,
56+
RunE: contextUseCmdFunc,
57+
}
58+
59+
rootCmd.AddCommand(contextCmd)
2160
contextCmd.AddCommand(contextListCmd)
2261
contextListCmd.Flags().Bool("reveal-tokens", false, "display secrets in results")
23-
2462
contextCmd.AddCommand(contextSetCmd)
2563
contextCmd.AddCommand(contextRemoveCmd)
2664
contextCmd.AddCommand(contextUseCmd)
2765
}
2866

29-
var contextCmd = &cobra.Command{
30-
Use: "context <subcommand>",
31-
Short: "Manage configurations for connecting to SpiceDB deployments",
32-
Aliases: []string{"ctx"},
33-
}
34-
35-
var contextListCmd = &cobra.Command{
36-
Use: "list",
37-
Short: "Lists all available contexts",
38-
Aliases: []string{"ls"},
39-
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
40-
ValidArgsFunction: cobra.NoFileCompletions,
41-
RunE: contextListCmdFunc,
42-
}
43-
44-
var contextSetCmd = &cobra.Command{
45-
Use: "set <name> <endpoint> <api-token>",
46-
Short: "Creates or overwrite a context",
47-
Args: commands.ValidationWrapper(cobra.ExactArgs(3)),
48-
ValidArgsFunction: cobra.NoFileCompletions,
49-
RunE: contextSetCmdFunc,
50-
}
51-
52-
var contextRemoveCmd = &cobra.Command{
53-
Use: "remove <system>",
54-
Short: "Removes a context",
55-
Aliases: []string{"rm"},
56-
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
57-
ValidArgsFunction: ContextGet,
58-
RunE: contextRemoveCmdFunc,
59-
}
60-
61-
var contextUseCmd = &cobra.Command{
62-
Use: "use <system>",
63-
Short: "Sets a context as the current context",
64-
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
65-
ValidArgsFunction: ContextGet,
66-
RunE: contextUseCmdFunc,
67-
}
68-
6967
func ContextGet(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
7068
_, secretStore := client.DefaultStorage()
7169
secrets, err := secretStore.Get()

internal/cmd/import.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,10 @@ import (
2222
)
2323

2424
func registerImportCmd(rootCmd *cobra.Command) {
25-
rootCmd.AddCommand(importCmd)
26-
importCmd.Flags().Int("batch-size", 1000, "import batch size")
27-
importCmd.Flags().Int("workers", 1, "number of concurrent batching workers")
28-
importCmd.Flags().Bool("schema", true, "import schema")
29-
importCmd.Flags().Bool("relationships", true, "import relationships")
30-
importCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before importing")
31-
}
32-
33-
var importCmd = &cobra.Command{
34-
Use: "import <url>",
35-
Short: "Imports schema and relationships from a file or url",
36-
Example: `
25+
importCmd := &cobra.Command{
26+
Use: "import <url>",
27+
Short: "Imports schema and relationships from a file or url",
28+
Example: `
3729
From a gist:
3830
zed import https://gist.github.com/ecordell/8e3b613a677e3c844742cf24421c08b6
3931
@@ -61,8 +53,16 @@ var importCmd = &cobra.Command{
6153
With schema definition prefix:
6254
zed import --schema-definition-prefix=mypermsystem file:///Users/zed/Downloads/authzed-x7izWU8_2Gw3.yaml
6355
`,
64-
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
65-
RunE: importCmdFunc,
56+
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
57+
RunE: importCmdFunc,
58+
}
59+
60+
rootCmd.AddCommand(importCmd)
61+
importCmd.Flags().Int("batch-size", 1000, "import batch size")
62+
importCmd.Flags().Int("workers", 1, "number of concurrent batching workers")
63+
importCmd.Flags().Bool("schema", true, "import schema")
64+
importCmd.Flags().Bool("relationships", true, "import relationships")
65+
importCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before importing")
6666
}
6767

6868
func importCmdFunc(cmd *cobra.Command, args []string) error {

internal/cmd/mcp.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,27 @@ import (
88
)
99

1010
func registerMCPCmd(rootCmd *cobra.Command) {
11-
rootCmd.AddCommand(mcpCmd)
12-
mcpCmd.AddCommand(mcpRunCmd)
13-
14-
mcpRunCmd.Flags().IntP("port", "p", 9999, "port for the HTTP streaming server")
15-
}
11+
mcpCmd := &cobra.Command{
12+
Use: "mcp <subcommand>",
13+
Short: "MCP (Model Context Protocol) server commands",
14+
Long: `MCP (Model Context Protocol) server commands.
1615
17-
var mcpCmd = &cobra.Command{
18-
Use: "mcp <subcommand>",
19-
Short: "MCP (Model Context Protocol) server commands",
20-
Long: `MCP (Model Context Protocol) server commands.
21-
2216
The MCP server provides tooling and resources for developing and debugging SpiceDB schema and relationships. The server runs an in-memory development instance of SpiceDB and does not connect to a running instance of SpiceDB.
2317
2418
To use with Claude Code, run ` + "`zed mcp experimental-run`" + ` to start the SpiceDB Dev MCP server and then run ` + "`claude mcp add --transport http spicedb \"http://localhost:9999/mcp\"`" + ` to add the server to your Claude Code integrations.`,
25-
}
19+
}
20+
21+
mcpRunCmd := &cobra.Command{
22+
Use: "experimental-run",
23+
Short: "Run the Experimental MCP server",
24+
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
25+
ValidArgsFunction: cobra.NoFileCompletions,
26+
RunE: mcpRunCmdFunc,
27+
}
2628

27-
var mcpRunCmd = &cobra.Command{
28-
Use: "experimental-run",
29-
Short: "Run the Experimental MCP server",
30-
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
31-
ValidArgsFunction: cobra.NoFileCompletions,
32-
RunE: mcpRunCmdFunc,
29+
rootCmd.AddCommand(mcpCmd)
30+
mcpCmd.AddCommand(mcpRunCmd)
31+
mcpRunCmd.Flags().IntP("port", "p", 9999, "port for the HTTP streaming server")
3332
}
3433

3534
func mcpRunCmdFunc(cmd *cobra.Command, _ []string) error {

internal/cmd/preview.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,19 @@ import (
44
"github.com/spf13/cobra"
55
)
66

7-
func registerPreviewCmd(rootCmd *cobra.Command) {
8-
rootCmd.AddCommand(previewCmd)
7+
func registerPreviewCmd(rootCmd *cobra.Command, schemaCompileCmd *cobra.Command) {
8+
previewCmd := &cobra.Command{
9+
Use: "preview <subcommand>",
10+
Short: "Experimental commands that have been made available for preview",
11+
}
912

10-
previewCmd.AddCommand(schemaCmd)
13+
schemaCmd := &cobra.Command{
14+
Use: "schema <subcommand>",
15+
Short: "Manage schema for a permissions system",
16+
Deprecated: "please use `zed schema compile`",
17+
}
1118

19+
rootCmd.AddCommand(previewCmd)
20+
previewCmd.AddCommand(schemaCmd)
1221
schemaCmd.AddCommand(schemaCompileCmd)
1322
}
14-
15-
var previewCmd = &cobra.Command{
16-
Use: "preview <subcommand>",
17-
Short: "Experimental commands that have been made available for preview",
18-
}
19-
20-
var schemaCmd = &cobra.Command{
21-
Use: "schema <subcommand>",
22-
Short: "Manage schema for a permissions system",
23-
Deprecated: "please use `zed schema compile`",
24-
}

0 commit comments

Comments
 (0)