Skip to content

Commit 755ccfe

Browse files
srtaalejzimeg
andauthored
feat(charm): add lipgloss styling with Slack brand colors under charm experiment (#365)
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent cf06058 commit 755ccfe

27 files changed

+996
-216
lines changed

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

cmd/manifest/validate_test.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ type ManifestValidatePkgMock struct {
3737
}
3838

3939
func (m *ManifestValidatePkgMock) ManifestValidate(ctx context.Context, clients *shared.ClientFactory, app types.App, token string) (bool, slackerror.Warnings, error) {
40-
m.Called(ctx, clients, app, token)
41-
return true, nil, nil
40+
args := m.Called(ctx, clients, app, token)
41+
return args.Bool(0), args.Get(1).(slackerror.Warnings), args.Error(2)
4242
}
4343

4444
func TestManifestValidateCommand(t *testing.T) {
@@ -63,7 +63,7 @@ func TestManifestValidateCommand(t *testing.T) {
6363
manifestValidatePkgMock := new(ManifestValidatePkgMock)
6464
manifestValidateFunc = manifestValidatePkgMock.ManifestValidate
6565

66-
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
66+
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(true, slackerror.Warnings{}, nil)
6767
err := cmd.ExecuteContext(ctx)
6868
if err != nil {
6969
assert.Fail(t, "cmd.Execute had unexpected error")
@@ -138,7 +138,7 @@ func TestManifestValidateCommand_HandleMissingAppInstallError_OneUserAuth(t *tes
138138
// Mock the manifest validate package
139139
manifestValidatePkgMock := new(ManifestValidatePkgMock)
140140
manifestValidateFunc = manifestValidatePkgMock.ManifestValidate
141-
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
141+
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(true, slackerror.Warnings{}, nil)
142142

143143
// Should execute without error
144144
err := cmd.ExecuteContext(ctx)
@@ -200,14 +200,38 @@ func TestManifestValidateCommand_HandleMissingAppInstallError_MoreThanOneUserAut
200200
// Mock the manifest validate package
201201
manifestValidatePkgMock := new(ManifestValidatePkgMock)
202202
manifestValidateFunc = manifestValidatePkgMock.ManifestValidate
203-
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
203+
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(true, slackerror.Warnings{}, nil)
204204

205205
// Should execute without error
206206
err := cmd.ExecuteContext(ctx)
207207
require.NoError(t, err)
208208
clientsMock.Auth.AssertCalled(t, "SetSelectedAuth", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
209209
}
210210

211+
func TestManifestValidateCommand_InvalidManifest(t *testing.T) {
212+
ctx := slackcontext.MockContext(t.Context())
213+
clientsMock := shared.NewClientsMock()
214+
clientsMock.AddDefaultMocks()
215+
216+
clients := shared.NewClientFactory(clientsMock.MockClientFactory(), func(clients *shared.ClientFactory) {
217+
clients.SDKConfig = hooks.NewSDKConfigMock()
218+
})
219+
220+
cmd := NewValidateCommand(clients)
221+
testutil.MockCmdIO(clients.IO, cmd)
222+
223+
appSelectMock := prompts.NewAppSelectMock()
224+
appSelectPromptFunc = appSelectMock.AppSelectPrompt
225+
appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil)
226+
227+
manifestValidatePkgMock := new(ManifestValidatePkgMock)
228+
manifestValidateFunc = manifestValidatePkgMock.ManifestValidate
229+
manifestValidatePkgMock.On("ManifestValidate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false, slackerror.Warnings{}, nil)
230+
231+
err := cmd.ExecuteContext(ctx)
232+
require.NoError(t, err)
233+
}
234+
211235
func TestManifestValidateCommand_HandleOtherErrors(t *testing.T) {
212236
// Create mocks
213237
ctx := slackcontext.MockContext(t.Context())

cmd/project/create_template_charm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"context"
1919
"strings"
2020

21-
"github.com/charmbracelet/huh"
21+
huh "charm.land/huh/v2"
2222
"github.com/slackapi/slack-cli/internal/shared"
2323
"github.com/slackapi/slack-cli/internal/slackerror"
2424
"github.com/slackapi/slack-cli/internal/slacktrace"

cmd/project/create_template_charm_test.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import (
1919
"fmt"
2020
"testing"
2121

22-
tea "github.com/charmbracelet/bubbletea"
23-
"github.com/charmbracelet/huh"
22+
tea "charm.land/bubbletea/v2"
23+
huh "charm.land/huh/v2"
2424
"github.com/charmbracelet/x/ansi"
2525
"github.com/slackapi/slack-cli/internal/shared"
2626
"github.com/stretchr/testify/assert"
@@ -77,7 +77,7 @@ func TestBuildTemplateSelectionForm(t *testing.T) {
7777
doAllUpdates(f, f.Init())
7878

7979
// Submit first option (Starter app -> getting-started)
80-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyEnter})
80+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
8181
doAllUpdates(f, cmd)
8282

8383
view := ansi.Strip(f.View())
@@ -95,13 +95,13 @@ func TestBuildTemplateSelectionForm(t *testing.T) {
9595
doAllUpdates(f, f.Init())
9696

9797
// Navigate down to "View more samples" (4th option, index 3)
98-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyDown})
98+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
9999
doAllUpdates(f, cmd)
100-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyDown})
100+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
101101
doAllUpdates(f, cmd)
102-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyDown})
102+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
103103
doAllUpdates(f, cmd)
104-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
104+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
105105
doAllUpdates(f, cmd)
106106

107107
assert.Equal(t, viewMoreSamples, category)
@@ -119,11 +119,11 @@ func TestBuildTemplateSelectionForm(t *testing.T) {
119119
doAllUpdates(f, f.Init())
120120

121121
// Navigate to Automation app (3rd option, index 2) and submit
122-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyDown})
122+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
123123
doAllUpdates(f, cmd)
124-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyDown})
124+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
125125
doAllUpdates(f, cmd)
126-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
126+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
127127
doAllUpdates(f, cmd)
128128

129129
view := ansi.Strip(f.View())
@@ -140,10 +140,10 @@ func TestBuildTemplateSelectionForm(t *testing.T) {
140140
doAllUpdates(f, f.Init())
141141

142142
// Select first category (Starter app)
143-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyEnter})
143+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
144144
doAllUpdates(f, cmd)
145145
// Select first template (Bolt for JavaScript)
146-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
146+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
147147
doAllUpdates(f, cmd)
148148

149149
assert.Equal(t, "slack-cli#getting-started", category)
@@ -176,10 +176,10 @@ func TestCharmPromptTemplateSelection(t *testing.T) {
176176
runForm = func(f *huh.Form) error {
177177
doAllUpdates(f, f.Init())
178178
// Select first category (Starter app)
179-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyEnter})
179+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
180180
doAllUpdates(f, cmd)
181181
// Select first template (Bolt for JavaScript)
182-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
182+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
183183
doAllUpdates(f, cmd)
184184
return nil
185185
}
@@ -212,16 +212,16 @@ func TestCharmPromptTemplateSelection(t *testing.T) {
212212
runForm = func(f *huh.Form) error {
213213
doAllUpdates(f, f.Init())
214214
// Navigate to "View more samples" (4th option)
215-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyDown})
215+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
216216
doAllUpdates(f, cmd)
217-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyDown})
217+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
218218
doAllUpdates(f, cmd)
219-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyDown})
219+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
220220
doAllUpdates(f, cmd)
221-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
221+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
222222
doAllUpdates(f, cmd)
223223
// Select "Browse sample gallery..."
224-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
224+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
225225
doAllUpdates(f, cmd)
226226
return nil
227227
}
@@ -240,12 +240,12 @@ func TestCharmPromptTemplateSelection(t *testing.T) {
240240
runForm = func(f *huh.Form) error {
241241
doAllUpdates(f, f.Init())
242242
// Navigate to "AI Agent app" (2nd option)
243-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyDown})
243+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyDown})
244244
doAllUpdates(f, cmd)
245-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
245+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
246246
doAllUpdates(f, cmd)
247247
// Select first template (Bolt for JavaScript)
248-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
248+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
249249
doAllUpdates(f, cmd)
250250
return nil
251251
}

cmd/project/create_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import (
1919
"fmt"
2020
"testing"
2121

22-
tea "github.com/charmbracelet/bubbletea"
23-
"github.com/charmbracelet/huh"
22+
tea "charm.land/bubbletea/v2"
23+
huh "charm.land/huh/v2"
2424
"github.com/slackapi/slack-cli/internal/config"
2525
"github.com/slackapi/slack-cli/internal/experiment"
2626
"github.com/slackapi/slack-cli/internal/iostreams"
@@ -573,9 +573,9 @@ func TestCreateCommand(t *testing.T) {
573573
runForm = func(f *huh.Form) error {
574574
doAllUpdates(f, f.Init())
575575
// Select first category (Starter app) then first template (Bolt for JS)
576-
_, cmd := f.Update(tea.KeyMsg{Type: tea.KeyEnter})
576+
_, cmd := f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
577577
doAllUpdates(f, cmd)
578-
_, cmd = f.Update(tea.KeyMsg{Type: tea.KeyEnter})
578+
_, cmd = f.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
579579
doAllUpdates(f, cmd)
580580
return nil
581581
}

cmd/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"strings"
2323
"syscall"
2424

25-
"github.com/charmbracelet/huh"
25+
huh "charm.land/huh/v2"
2626
"github.com/slackapi/slack-cli/cmd/app"
2727
"github.com/slackapi/slack-cli/cmd/auth"
2828
"github.com/slackapi/slack-cli/cmd/collaborators"
@@ -45,6 +45,7 @@ import (
4545
"github.com/slackapi/slack-cli/cmd/upgrade"
4646
versioncmd "github.com/slackapi/slack-cli/cmd/version"
4747
"github.com/slackapi/slack-cli/internal/cmdutil"
48+
"github.com/slackapi/slack-cli/internal/experiment"
4849
"github.com/slackapi/slack-cli/internal/iostreams"
4950
"github.com/slackapi/slack-cli/internal/pkg/version"
5051
"github.com/slackapi/slack-cli/internal/shared"
@@ -299,6 +300,7 @@ func InitConfig(ctx context.Context, clients *shared.ClientFactory, rootCmd *cob
299300

300301
// Init configurations
301302
clients.Config.LoadExperiments(ctx, clients.IO.PrintDebug)
303+
style.ToggleCharm(clients.Config.WithExperimentOn(experiment.Charm))
302304
// TODO(slackcontext) Consolidate storing CLI version to slackcontext
303305
clients.Config.Version = clients.CLIVersion
304306

cmd/upgrade/upgrade.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ func checkForUpdates(clients *shared.ClientFactory, cmd *cobra.Command) error {
7474
}
7575

7676
if clients.SDKConfig.Hooks.CheckUpdate.IsAvailable() {
77-
cmd.Printf("%s You are using the latest Slack CLI and SDK versions\n", style.Styler().Green("✔").String())
77+
cmd.Printf("%s You are using the latest Slack CLI and SDK versions\n", style.Green("✔"))
7878
} else {
79-
cmd.Printf("%s You are using the latest Slack CLI version\n", style.Styler().Green("✔").String())
79+
cmd.Printf("%s You are using the latest Slack CLI version\n", style.Green("✔"))
8080
}
8181

8282
return nil

0 commit comments

Comments
 (0)