Skip to content

Commit 6f49023

Browse files
committed
fix: gate manifest sync command behind experiment
Hides the slack manifest sync subcommand and the slack sync alias from --help, and refuses execution unless the manifest-sync experiment is enabled. Adds ErrExperimentRequired so the user gets a clear remediation pointing at the --experiment flag.
1 parent 38f720f commit 6f49023

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

cmd/manifest/sync.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"github.com/opentracing/opentracing-go"
55
"github.com/slackapi/slack-cli/internal/app"
66
"github.com/slackapi/slack-cli/internal/cmdutil"
7+
"github.com/slackapi/slack-cli/internal/experiment"
78
"github.com/slackapi/slack-cli/internal/pkg/manifest"
89
"github.com/slackapi/slack-cli/internal/prompts"
910
"github.com/slackapi/slack-cli/internal/shared"
11+
"github.com/slackapi/slack-cli/internal/slackerror"
1012
"github.com/slackapi/slack-cli/internal/style"
1113
"github.com/spf13/cobra"
1214
)
@@ -15,14 +17,22 @@ var manifestSyncFunc = manifest.Sync
1517

1618
func NewSyncCommand(clients *shared.ClientFactory) *cobra.Command {
1719
cmd := &cobra.Command{
18-
Use: "sync",
19-
Short: "Sync the app manifest between project and app settings",
20-
Long: "Compare the local project manifest with app settings, resolve differences, and sync both to the same state.",
20+
Use: "sync",
21+
Short: "Sync the app manifest between project and app settings",
22+
Long: "Compare the local project manifest with app settings, resolve differences, and sync both to the same state.",
23+
Hidden: true,
2124
Example: style.ExampleCommandsf([]style.ExampleCommand{
2225
{Command: "manifest sync", Meaning: "Sync project manifest with app settings"},
2326
}),
2427
Args: cobra.NoArgs,
2528
PreRunE: func(cmd *cobra.Command, args []string) error {
29+
if !clients.Config.WithExperimentOn(experiment.ManifestSync) {
30+
return slackerror.New(slackerror.ErrExperimentRequired).
31+
WithRemediation("Enable the %s experiment with %s",
32+
style.Highlight(string(experiment.ManifestSync)),
33+
style.CommandText("--experiment manifest-sync"),
34+
)
35+
}
2636
return cmdutil.IsValidProjectDirectory(clients)
2737
},
2838
RunE: func(cmd *cobra.Command, args []string) error {

cmd/manifest/sync_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2022-2026 Salesforce, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package manifest
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/slackapi/slack-cli/internal/experiment"
22+
"github.com/slackapi/slack-cli/internal/shared"
23+
"github.com/slackapi/slack-cli/internal/slackerror"
24+
"github.com/slackapi/slack-cli/test/testutil"
25+
"github.com/spf13/cobra"
26+
)
27+
28+
func TestSyncCommand(t *testing.T) {
29+
testutil.TableTestCommand(t, testutil.CommandTests{
30+
"errors when the manifest-sync experiment is off": {
31+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
32+
cm.AddDefaultMocks()
33+
cf.Config.LoadExperiments(ctx, cf.IO.PrintDebug)
34+
},
35+
ExpectedError: slackerror.New(slackerror.ErrExperimentRequired),
36+
},
37+
"passes the experiment gate when manifest-sync is enabled via flag": {
38+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
39+
cm.AddDefaultMocks()
40+
cf.Config.ExperimentsFlag = []string{string(experiment.ManifestSync)}
41+
cf.Config.LoadExperiments(ctx, cf.IO.PrintDebug)
42+
},
43+
// We expect the command to fail downstream of the gate (no app
44+
// selected, no SDK config), but NOT with ErrCommandUnavailable —
45+
// the gate itself should pass.
46+
ExpectedErrorStrings: []string{},
47+
},
48+
}, func(clients *shared.ClientFactory) *cobra.Command {
49+
return NewSyncCommand(clients)
50+
})
51+
}

internal/slackerror/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const (
104104
ErrDotEnvPlaceholderNotFound = "dotenv_placeholder_not_found"
105105
ErrDotEnvVarMarshal = "dotenv_var_marshal_error"
106106
ErrEnterpriseNotFound = "enterprise_not_found"
107+
ErrExperimentRequired = "experiment_required"
107108
ErrFailForSomeRequests = "failed_for_some_requests"
108109
ErrFailToGetTeamsForRestrictedUser = "fail_to_get_teams_for_restricted_user"
109110
ErrFailedAddingCollaborator = "failed_adding_collaborator"
@@ -733,6 +734,11 @@ Otherwise start your app for local development with: %s`,
733734
Message: "The `enterprise` was not found",
734735
},
735736

737+
ErrExperimentRequired: {
738+
Code: ErrExperimentRequired,
739+
Message: "This command requires an experiment to be enabled",
740+
},
741+
736742
ErrFailForSomeRequests: {
737743
Code: ErrFailForSomeRequests,
738744
Message: "At least one request was not cancelled",

0 commit comments

Comments
 (0)