Skip to content

Commit b395335

Browse files
merge conflicts
2 parents bd57fbb + 8e4c0aa commit b395335

68 files changed

Lines changed: 3591 additions & 1181 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/app/add.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/slackapi/slack-cli/internal/app"
2121
"github.com/slackapi/slack-cli/internal/cmdutil"
2222
"github.com/slackapi/slack-cli/internal/config"
23-
"github.com/slackapi/slack-cli/internal/experiment"
2423
"github.com/slackapi/slack-cli/internal/pkg/apps"
2524
"github.com/slackapi/slack-cli/internal/prompts"
2625
"github.com/slackapi/slack-cli/internal/shared"
@@ -83,9 +82,6 @@ func preRunAddCommand(ctx context.Context, clients *shared.ClientFactory, cmd *c
8382
if err != nil {
8483
return err
8584
}
86-
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
87-
return nil
88-
}
8985
clients.Config.SetFlags(cmd)
9086
return nil
9187
}

cmd/app/add_test.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/slackapi/slack-cli/internal/cache"
2424
"github.com/slackapi/slack-cli/internal/cmdutil"
2525
"github.com/slackapi/slack-cli/internal/config"
26-
"github.com/slackapi/slack-cli/internal/experiment"
2726
"github.com/slackapi/slack-cli/internal/iostreams"
2827
"github.com/slackapi/slack-cli/internal/prompts"
2928
"github.com/slackapi/slack-cli/internal/shared"
@@ -85,25 +84,21 @@ func TestAppAddCommandPreRun(t *testing.T) {
8584
cf.SDKConfig.WorkingDirectory = "."
8685
},
8786
},
88-
"proceeds if manifest.source is local with the bolt experiment": {
87+
"proceeds if manifest.source is local": {
8988
ExpectedError: nil,
9089
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
9190
cf.SDKConfig.WorkingDirectory = "."
9291
cm.AddDefaultMocks()
93-
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
94-
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
9592
mockProjectConfig := config.NewProjectConfigMock()
9693
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
9794
cm.Config.ProjectConfig = mockProjectConfig
9895
},
9996
},
100-
"proceeds if manifest.source is remote with the bolt experiment": {
97+
"proceeds if manifest.source is remote": {
10198
ExpectedError: nil,
10299
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
103100
cf.SDKConfig.WorkingDirectory = "."
104101
cm.AddDefaultMocks()
105-
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
106-
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
107102
mockProjectConfig := config.NewProjectConfigMock()
108103
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceRemote, nil)
109104
cm.Config.ProjectConfig = mockProjectConfig
@@ -207,6 +202,7 @@ func TestAppAddCommand(t *testing.T) {
207202
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
208203
Return(nil)
209204
mockProjectConfig := config.NewProjectConfigMock()
205+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
210206
mockProjectConfig.On("Cache").Return(mockProjectCache)
211207
cm.Config.ProjectConfig = mockProjectConfig
212208
},
@@ -279,6 +275,7 @@ func TestAppAddCommand(t *testing.T) {
279275
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
280276
Return(nil)
281277
mockProjectConfig := config.NewProjectConfigMock()
278+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
282279
mockProjectConfig.On("Cache").Return(mockProjectCache)
283280
cm.Config.ProjectConfig = mockProjectConfig
284281
},
@@ -343,7 +340,7 @@ func TestAppAddCommand(t *testing.T) {
343340
nil,
344341
)
345342

346-
// Mock existing and updated cache
343+
// Mock existing and updated cache - hashes must match for update to proceed
347344
cm.API.On(
348345
"ExportAppManifest",
349346
mock.Anything,
@@ -357,10 +354,11 @@ func TestAppAddCommand(t *testing.T) {
357354
mockProjectCache.On("GetManifestHash", mock.Anything, mock.Anything).
358355
Return(cache.Hash("b4b4"), nil)
359356
mockProjectCache.On("NewManifestHash", mock.Anything, mock.Anything).
360-
Return(cache.Hash("xoxo"), nil)
357+
Return(cache.Hash("b4b4"), nil) // matching hash allows update to proceed
361358
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
362359
Return(nil)
363360
mockProjectConfig := config.NewProjectConfigMock()
361+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
364362
mockProjectConfig.On("Cache").Return(mockProjectCache)
365363
cm.Config.ProjectConfig = mockProjectConfig
366364
},
@@ -428,6 +426,7 @@ func TestAppAddCommand(t *testing.T) {
428426
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
429427
Return(nil)
430428
mockProjectConfig := config.NewProjectConfigMock()
429+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
431430
mockProjectConfig.On("Cache").Return(mockProjectCache)
432431
cm.Config.ProjectConfig = mockProjectConfig
433432
},
@@ -515,6 +514,7 @@ func TestAppAddCommand(t *testing.T) {
515514
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
516515
Return(nil)
517516
mockProjectConfig := config.NewProjectConfigMock()
517+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
518518
mockProjectConfig.On("Cache").Return(mockProjectCache)
519519
cm.Config.ProjectConfig = mockProjectConfig
520520
},
@@ -599,6 +599,7 @@ func TestAppAddCommand(t *testing.T) {
599599
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
600600
Return(nil)
601601
mockProjectConfig := config.NewProjectConfigMock()
602+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
602603
mockProjectConfig.On("Cache").Return(mockProjectCache)
603604
cm.Config.ProjectConfig = mockProjectConfig
604605
},
@@ -685,6 +686,7 @@ func TestAppAddCommand(t *testing.T) {
685686
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
686687
Return(nil)
687688
mockProjectConfig := config.NewProjectConfigMock()
689+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
688690
mockProjectConfig.On("Cache").Return(mockProjectCache)
689691
cm.Config.ProjectConfig = mockProjectConfig
690692
},
@@ -760,6 +762,7 @@ func TestAppAddCommand(t *testing.T) {
760762
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
761763
Return(nil)
762764
mockProjectConfig := config.NewProjectConfigMock()
765+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
763766
mockProjectConfig.On("Cache").Return(mockProjectCache)
764767
cm.Config.ProjectConfig = mockProjectConfig
765768
},
@@ -816,6 +819,7 @@ func TestAppAddCommand(t *testing.T) {
816819
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
817820
Return(nil)
818821
mockProjectConfig := config.NewProjectConfigMock()
822+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
819823
mockProjectConfig.On("Cache").Return(mockProjectCache)
820824
cm.Config.ProjectConfig = mockProjectConfig
821825
},
@@ -872,6 +876,7 @@ func TestAppAddCommand(t *testing.T) {
872876
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
873877
Return(nil)
874878
mockProjectConfig := config.NewProjectConfigMock()
879+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
875880
mockProjectConfig.On("Cache").Return(mockProjectCache)
876881
cm.Config.ProjectConfig = mockProjectConfig
877882
},

cmd/app/link.go

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import (
1818
"context"
1919
"fmt"
2020
"path/filepath"
21-
"slices"
2221
"strings"
2322

2423
"github.com/slackapi/slack-cli/internal/cmdutil"
2524
"github.com/slackapi/slack-cli/internal/config"
2625
"github.com/slackapi/slack-cli/internal/iostreams"
2726
"github.com/slackapi/slack-cli/internal/pkg/apps"
27+
"github.com/slackapi/slack-cli/internal/prompts"
2828
"github.com/slackapi/slack-cli/internal/shared"
2929
"github.com/slackapi/slack-cli/internal/shared/types"
3030
"github.com/slackapi/slack-cli/internal/slackerror"
@@ -262,7 +262,7 @@ func LinkAppFooterSection(ctx context.Context, clients *shared.ClientFactory, ap
262262

263263
// promptExistingApp gathers details to represent app information
264264
func promptExistingApp(ctx context.Context, clients *shared.ClientFactory) (types.App, *types.SlackAuth, error) {
265-
slackAuth, err := promptTeamSlackAuth(ctx, clients)
265+
slackAuth, err := prompts.PromptTeamSlackAuth(ctx, clients, "Select the existing app team")
266266
if err != nil {
267267
return types.App{}, &types.SlackAuth{}, err
268268
}
@@ -291,61 +291,6 @@ func promptExistingApp(ctx context.Context, clients *shared.ClientFactory) (type
291291
return apps[0], slackAuth, nil
292292
}
293293

294-
// promptTeamSlackAuth retrieves an authenticated team from input
295-
func promptTeamSlackAuth(ctx context.Context, clients *shared.ClientFactory) (*types.SlackAuth, error) {
296-
allAuths, err := clients.Auth().Auths(ctx)
297-
if err != nil {
298-
return &types.SlackAuth{}, err
299-
}
300-
slices.SortFunc(allAuths, func(i, j types.SlackAuth) int {
301-
if i.TeamDomain == j.TeamDomain {
302-
return strings.Compare(i.TeamID, j.TeamID)
303-
}
304-
return strings.Compare(i.TeamDomain, j.TeamDomain)
305-
})
306-
var teamLabels []string
307-
for _, auth := range allAuths {
308-
teamLabels = append(
309-
teamLabels,
310-
style.TeamSelectLabel(auth.TeamDomain, auth.TeamID),
311-
)
312-
}
313-
selection, err := clients.IO.SelectPrompt(
314-
ctx,
315-
"Select the existing app team",
316-
teamLabels,
317-
iostreams.SelectPromptConfig{
318-
Required: true,
319-
Flag: clients.Config.Flags.Lookup("team"),
320-
},
321-
)
322-
if err != nil {
323-
return &types.SlackAuth{}, err
324-
}
325-
if selection.Prompt {
326-
clients.Auth().SetSelectedAuth(ctx, allAuths[selection.Index], clients.Config, clients.Os)
327-
return &allAuths[selection.Index], nil
328-
}
329-
teamMatch := false
330-
teamIndex := -1
331-
for ii, auth := range allAuths {
332-
if selection.Option == auth.TeamID || selection.Option == auth.TeamDomain {
333-
if teamMatch {
334-
return &types.SlackAuth{}, slackerror.New(slackerror.ErrMissingAppTeamID).
335-
WithMessage("The team cannot be determined by team domain").
336-
WithRemediation("Provide the team ID for the installed app")
337-
}
338-
teamMatch = true
339-
teamIndex = ii
340-
}
341-
}
342-
if !teamMatch {
343-
return &types.SlackAuth{}, slackerror.New(slackerror.ErrCredentialsNotFound)
344-
}
345-
clients.Auth().SetSelectedAuth(ctx, allAuths[teamIndex], clients.Config, clients.Os)
346-
return &allAuths[teamIndex], nil
347-
}
348-
349294
// promptAppID retrieves an app ID from user input
350295
func promptAppID(ctx context.Context, clients *shared.ClientFactory) (string, error) {
351296
if clients.Config.Flags.Lookup("app").Changed {

cmd/datastore/query_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,55 @@ func prepareExportMockData(cm *shared.ClientsMock, numberOfItems int, maxItemsTo
465465
}
466466
return data, nil
467467
}
468+
469+
func Test_getExpressionPatterns(t *testing.T) {
470+
tests := map[string]struct {
471+
expression string
472+
wantAttrs int
473+
wantVals int
474+
}{
475+
"expression with attributes and values": {
476+
expression: "#name = :name AND #status = :status",
477+
wantAttrs: 2,
478+
wantVals: 2,
479+
},
480+
"empty expression": {
481+
expression: "",
482+
wantAttrs: 0,
483+
wantVals: 0,
484+
},
485+
}
486+
for name, tc := range tests {
487+
t.Run(name, func(t *testing.T) {
488+
attrs, vals := getExpressionPatterns(tc.expression)
489+
assert.Len(t, attrs, tc.wantAttrs)
490+
assert.Len(t, vals, tc.wantVals)
491+
})
492+
}
493+
}
494+
495+
func Test_mapAttributeFlag(t *testing.T) {
496+
tests := map[string]struct {
497+
flag string
498+
wantErr bool
499+
}{
500+
"valid JSON": {
501+
flag: `{"#name":"name"}`,
502+
},
503+
"invalid JSON": {
504+
flag: `not json`,
505+
wantErr: true,
506+
},
507+
}
508+
for name, tc := range tests {
509+
t.Run(name, func(t *testing.T) {
510+
result, err := mapAttributeFlag(tc.flag)
511+
if tc.wantErr {
512+
assert.Error(t, err)
513+
} else {
514+
assert.NoError(t, err)
515+
assert.NotNil(t, result)
516+
}
517+
})
518+
}
519+
}

cmd/help/help.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func HelpFunc(
3535
if help, _ := clients.Config.Flags.GetBool("help"); help {
3636
clients.Config.LoadExperiments(ctx, clients.IO.PrintDebug)
3737
}
38+
style.ToggleCharm(clients.Config.WithExperimentOn(experiment.Charm))
3839
experiments := []string{}
3940
for _, exp := range clients.Config.GetExperiments() {
4041
if experiment.Includes(exp) {
@@ -66,15 +67,64 @@ func PrintHelpTemplate(cmd *cobra.Command, data style.TemplateData) {
6667
cmd.PrintErrln(err)
6768
}
6869
cmd.Long = cmdLongF.String()
69-
tmpl := helpTemplate
70+
tmpl := legacyHelpTemplate
71+
if style.IsCharmEnabled() {
72+
tmpl = charmHelpTemplate
73+
}
7074
err = style.PrintTemplate(cmd.OutOrStdout(), tmpl, templateInfo{cmd, data})
7175
if err != nil {
7276
cmd.PrintErrln(err)
7377
}
7478
}
7579

76-
// helpTemplate formats values and information for a helpful output
77-
const helpTemplate string = `{{.Long}}
80+
// ════════════════════════════════════════════════════════════════════════════════
81+
// Charm help template — lipgloss styling
82+
// ════════════════════════════════════════════════════════════════════════════════
83+
84+
const charmHelpTemplate string = `{{.Long | ToDescription}}
85+
86+
{{Header "Usage"}}{{if .Runnable}}
87+
{{ToPrompt "$ "}}{{ToCommandText .UseLine}}{{end}}{{if gt (len .Aliases) 0}}
88+
89+
{{Header "Aliases"}}
90+
{{.NameAndAliases | ToCommandText}}{{end}}{{if .HasAvailableSubCommands}}
91+
92+
{{if eq .Name (GetProcessName)}}{{Header "Commands"}}{{range .Commands}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}
93+
{{.Name | ToGroupName }}{{range .Commands}}{{if (not .Hidden)}}
94+
{{rpad .Name .NamePadding | ToCommandText}} {{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{range .Commands}}{{if and (not .HasAvailableSubCommands) (not .Hidden)}}{{if not (IsAlias .Name $.Data.Aliases)}}
95+
{{(rpad .Name .NamePadding) | ToGroupName }}{{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{else}}{{Header "Subcommands"}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{range .Commands}}{{if not .HasAvailableSubCommands}}
96+
{{(rpad .Name .NamePadding) | ToCommandText }} {{.Short | ToDescription}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
97+
98+
{{Header "Flags"}}
99+
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces | ToFlags}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}{{if or (HasAliasSubcommands .Name .Data.Aliases) (eq .Name (GetProcessName))}}
100+
101+
{{Header "Global aliases"}}{{range .Commands}}{{if and (IsAlias .Name $.Data.Aliases) (not .Hidden)}}
102+
{{(rpad .Name .NamePadding) | ToGroupName }} {{rpad (AliasParent .Name $.Data.Aliases) AliasPadding | ToAliasParent}} {{ToPrompt "❱"}} {{.Name | ToGroupName}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableInheritedFlags}}
103+
104+
{{Header "Global flags"}}
105+
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces | ToFlags}}{{end}}{{if .HasExample}}
106+
107+
{{Header "Example"}}
108+
{{ Examples .Example}}{{end}}{{if and (.HasAvailableSubCommands) (not .Hidden)}}
109+
110+
{{Header "Experiments"}}
111+
{{ Experiments .Data.Experiments }}
112+
113+
{{Header "Additional help"}}
114+
{{ToSecondary "For more information about a specific command, run:"}}
115+
{{ToPrompt "$ "}}{{ToCommandText .CommandPath}}{{if eq .Name (GetProcessName)}}{{ToCommandText " <command>"}}{{end}}{{ToCommandText " <subcommand> --help"}}
116+
117+
{{ToSecondary "For guides and documentation, head over to "}}{{LinkText "https://docs.slack.dev/tools/slack-cli"}}{{end}}
118+
119+
`
120+
121+
// ════════════════════════════════════════════════════════════════════════════════
122+
// DEPRECATED: Legacy help template — aurora styling
123+
//
124+
// Delete this entire block when the charm experiment is permanently enabled.
125+
// ════════════════════════════════════════════════════════════════════════════════
126+
127+
const legacyHelpTemplate string = `{{.Long}}
78128
79129
{{Header "Usage"}}{{if .Runnable}}
80130
$ {{.UseLine}}{{end}}{{if gt (len .Aliases) 0}}

cmd/manifest/validate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ func NewValidateCommand(clients *shared.ClientFactory) *cobra.Command {
8989
cmd.Printf(
9090
"\n%s: %s\n",
9191
style.Bold("App Manifest Validation Result"),
92-
style.Styler().Green("Valid"),
92+
style.Green("Valid"),
9393
)
9494
clients.IO.PrintTrace(ctx, slacktrace.ManifestValidateSuccess)
9595
} else {
9696
cmd.Printf(
9797
"\n%s: %s\n",
9898
style.Bold("App Manifest Validation Result"),
99-
style.Styler().Red("InValid"),
99+
style.Red("InValid"),
100100
)
101101
}
102102
return nil

0 commit comments

Comments
 (0)