From 02787354d33ee33839a077338b310151f44f6592 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Thu, 4 Jun 2026 15:05:00 -0500 Subject: [PATCH] feat(cli): gate pro commands behind DEVSY_PRO_ENABLED The pro feature is not ready for general use. Hide the `pro` command tree from the CLI by default to avoid user confusion, and add an opt-in `DEVSY_PRO_ENABLED=true` env var to re-expose it for internal testing. --- cmd/env_flags_test.go | 23 +++++++++++++++++++++++ cmd/root.go | 35 ++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/cmd/env_flags_test.go b/cmd/env_flags_test.go index 073a830f0..9f048d901 100644 --- a/cmd/env_flags_test.go +++ b/cmd/env_flags_test.go @@ -19,6 +19,7 @@ const ( // setting the corresponding DEVSY_* env var pushes the value into the bound // pflag at registration time, satisfying cobra's required-flag check. func TestOptInEnvFlags_AppliesEnvValueToFlag(t *testing.T) { + t.Setenv(envProEnabled, "true") cases := []struct { cmdPath string flagName string @@ -84,6 +85,7 @@ func TestOptInEnvFlags_NoEnvLeavesDefault(t *testing.T) { // BindEnv: setting DEVSY_HOST="" must NOT mark the flag as Changed (otherwise // MarkFlagRequired would be satisfied by an empty value). func TestOptInEnvFlags_EmptyEnvIsNoOp(t *testing.T) { + t.Setenv(envProEnabled, "true") t.Setenv(envDevsyHost, "") rootCmd, _ := BuildRoot() cmd := resolveCommand(t, rootCmd, "pro workspace list") @@ -96,6 +98,7 @@ func TestOptInEnvFlags_EmptyEnvIsNoOp(t *testing.T) { // wins over a value supplied via DEVSY_*. This is the standard precedence // users expect: flag > env > default. func TestOptInEnvFlags_CLIOverridesEnv(t *testing.T) { + t.Setenv(envProEnabled, "true") t.Setenv(envDevsyHost, "env-value") rootCmd, _ := BuildRoot() // Replace the leaf command's RunE so Execute() doesn't try to actually @@ -116,6 +119,7 @@ func TestOptInEnvFlags_CLIOverridesEnv(t *testing.T) { // ValidateRequiredFlags pipeline to prove DEVSY_HOST satisfies // MarkFlagRequired("host") at execution time, not just in static inspection. func TestOptInEnvFlags_EnvSatisfiesRequired(t *testing.T) { + t.Setenv(envProEnabled, "true") t.Setenv(envDevsyHost, "from-env.example.com") rootCmd, _ := BuildRoot() leaf := resolveCommand(t, rootCmd, "pro workspace list") @@ -134,6 +138,7 @@ func TestOptInEnvFlags_EnvSatisfiesRequired(t *testing.T) { // This catches the regression where a future addition to cmd/pro/ forgets // the inline BindEnv call. func TestOptInEnvFlags_ProSubcommandSweep(t *testing.T) { + t.Setenv(envProEnabled, "true") for _, env := range []string{envDevsyHost, envDevsyProject} { t.Setenv(env, "sweep-"+strings.ToLower(env)) } @@ -157,6 +162,24 @@ func TestOptInEnvFlags_ProSubcommandSweep(t *testing.T) { walk(proCmd) } +// TestProCommand_HiddenWhenDisabled verifies pro is gated off by default. +func TestProCommand_HiddenWhenDisabled(t *testing.T) { + t.Setenv(envProEnabled, "") + rootCmd, _ := BuildRoot() + for _, c := range rootCmd.Commands() { + use := c.Use + if i := strings.IndexByte(use, ' '); i >= 0 { + use = use[:i] + } + require.NotEqual( + t, + "pro", + use, + "pro command should not be registered when DEVSY_PRO_ENABLED is unset", + ) + } +} + func resolveCommand(t *testing.T, rootCmd *cobra.Command, path string) *cobra.Command { t.Helper() if path == "" { diff --git a/cmd/root.go b/cmd/root.go index 081b93e00..4d0ab9a5a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -42,8 +42,17 @@ const ( groupPlatform = "platform" groupDevcontainer = "devcontainer" groupMeta = "meta" + + // envProEnabled gates registration of the `pro` command tree. The pro + // feature is not ready for general use; set DEVSY_PRO_ENABLED=true to + // expose it (e.g. for internal testing). + envProEnabled = "DEVSY_PRO_ENABLED" ) +func proEnabled() bool { + return os.Getenv(envProEnabled) == "true" +} + // isMachineLogFormat reports whether the configured --log-output mode produces // a structured, machine-parseable stream (json or logfmt). Callers use this to // suppress decorative human-readable affordances that would corrupt the stream. @@ -163,13 +172,16 @@ func BuildRoot() (*cobra.Command, *flags.GlobalFlags) { return nil } - rootCmd.AddGroup( - &cobra.Group{ID: groupCore, Title: "Core commands:"}, - &cobra.Group{ID: groupConfig, Title: "Configuration commands:"}, - &cobra.Group{ID: groupPlatform, Title: "Platform commands:"}, - &cobra.Group{ID: groupDevcontainer, Title: "Devcontainer commands:"}, - &cobra.Group{ID: groupMeta, Title: "Meta:"}, - ) + groups := []*cobra.Group{ + {ID: groupCore, Title: "Core commands:"}, + {ID: groupConfig, Title: "Configuration commands:"}, + {ID: groupDevcontainer, Title: "Devcontainer commands:"}, + {ID: groupMeta, Title: "Meta:"}, + } + if proEnabled() { + groups = append(groups, &cobra.Group{ID: groupPlatform, Title: "Platform commands:"}) + } + rootCmd.AddGroup(groups...) registerSubcommands(rootCmd, globalFlags) @@ -189,10 +201,11 @@ func registerSubcommands(rootCmd *cobra.Command, globalFlags *flags.GlobalFlags) contextCmd := context.NewContextCmd(globalFlags) contextCmd.GroupID = groupConfig rootCmd.AddCommand(contextCmd) - proCmd := pro.NewProCmd(globalFlags) - proCmd.GroupID = groupPlatform - rootCmd.AddCommand(proCmd) - + if proEnabled() { + proCmd := pro.NewProCmd(globalFlags) + proCmd.GroupID = groupPlatform + rootCmd.AddCommand(proCmd) + } wsCmd := wsCmdPkg.NewWorkspaceCmd(globalFlags) wsCmd.GroupID = groupCore rootCmd.AddCommand(wsCmd)