Skip to content

Commit 90dff7c

Browse files
adds command
1 parent 4fb7b4f commit 90dff7c

4 files changed

Lines changed: 215 additions & 0 deletions

File tree

cmd/docs/docs.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 docs
16+
17+
import (
18+
"fmt"
19+
"net/url"
20+
21+
"github.com/slackapi/slack-cli/internal/shared"
22+
"github.com/slackapi/slack-cli/internal/slacktrace"
23+
"github.com/slackapi/slack-cli/internal/style"
24+
"github.com/spf13/cobra"
25+
)
26+
27+
var searchFlag string
28+
29+
func NewCommand(clients *shared.ClientFactory) *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "docs",
32+
Short: "Open Slack developer docs",
33+
Long: "Open the Slack developer docs in your browser, with optional search functionality",
34+
Example: style.ExampleCommandsf([]style.ExampleCommand{
35+
{
36+
Meaning: "Open Slack developer docs homepage",
37+
Command: "docs",
38+
},
39+
{
40+
Meaning: "Search Slack developer docs",
41+
Command: "docs --search 'Block Kit'",
42+
},
43+
}),
44+
Args: cobra.NoArgs,
45+
RunE: func(cmd *cobra.Command, args []string) error {
46+
return runDocsCommand(clients, cmd, args)
47+
},
48+
}
49+
50+
cmd.Flags().StringVar(&searchFlag, "search", "", "search query for Slack documentation")
51+
52+
return cmd
53+
}
54+
55+
// runDocsCommand opens Slack developer documentation in the browser
56+
func runDocsCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []string) error {
57+
ctx := cmd.Context()
58+
59+
var docsURL string
60+
var sectionText string
61+
62+
if searchFlag != "" {
63+
// Build search URL
64+
searchQuery := url.QueryEscape(searchFlag)
65+
docsURL = fmt.Sprintf("https://docs.slack.dev/search/?q=%s", searchQuery)
66+
sectionText = fmt.Sprintf("Searching Slack developer docs: \"%s\"", searchFlag)
67+
} else {
68+
// Default docs homepage
69+
docsURL = "https://docs.slack.dev"
70+
sectionText = "Slack developer docs"
71+
}
72+
73+
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
74+
Emoji: "books",
75+
Text: sectionText,
76+
Secondary: []string{
77+
docsURL,
78+
},
79+
}))
80+
81+
clients.Browser().OpenURL(docsURL)
82+
83+
// Add trace for analytics
84+
if searchFlag != "" {
85+
clients.IO.PrintTrace(ctx, slacktrace.DocsSearchSuccess, searchFlag)
86+
} else {
87+
clients.IO.PrintTrace(ctx, slacktrace.DocsSuccess)
88+
}
89+
90+
return nil
91+
}

cmd/docs/docs_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 docs
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/slackapi/slack-cli/internal/shared"
22+
"github.com/slackapi/slack-cli/internal/slacktrace"
23+
"github.com/slackapi/slack-cli/test/testutil"
24+
"github.com/spf13/cobra"
25+
"github.com/stretchr/testify/mock"
26+
)
27+
28+
func Test_Docs_DocsCommand(t *testing.T) {
29+
testutil.TableTestCommand(t, testutil.CommandTests{
30+
"opens docs homepage without search": {
31+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
32+
// No special setup needed for basic functionality
33+
},
34+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
35+
expectedURL := "https://docs.slack.dev"
36+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
37+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSuccess, mock.Anything)
38+
},
39+
ExpectedOutputs: []string{
40+
"Slack developer docs",
41+
"https://docs.slack.dev",
42+
},
43+
},
44+
"opens docs with basic search query": {
45+
CmdArgs: []string{"--search", "Block Kit"},
46+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
47+
expectedURL := "https://docs.slack.dev/search/?q=Block+Kit"
48+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
49+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSearchSuccess, mock.Anything)
50+
},
51+
ExpectedOutputs: []string{
52+
"Searching Slack developer docs: \"Block Kit\"",
53+
"https://docs.slack.dev/search/?q=Block+Kit",
54+
},
55+
},
56+
"handles search query with multiple words": {
57+
CmdArgs: []string{"--search", "socket mode authentication"},
58+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
59+
expectedURL := "https://docs.slack.dev/search/?q=socket+mode+authentication"
60+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
61+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSearchSuccess, mock.Anything)
62+
},
63+
ExpectedOutputs: []string{
64+
"Searching Slack developer docs: \"socket mode authentication\"",
65+
"https://docs.slack.dev/search/?q=socket+mode+authentication",
66+
},
67+
},
68+
"handles special characters in search query": {
69+
CmdArgs: []string{"--search", "API & webhooks"},
70+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
71+
expectedURL := "https://docs.slack.dev/search/?q=API+%26+webhooks"
72+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
73+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSearchSuccess, mock.Anything)
74+
},
75+
ExpectedOutputs: []string{
76+
"Searching Slack developer docs: \"API & webhooks\"",
77+
"https://docs.slack.dev/search/?q=API+%26+webhooks",
78+
},
79+
},
80+
"handles search query with quotes": {
81+
CmdArgs: []string{"--search", "function \"hello world\""},
82+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
83+
expectedURL := "https://docs.slack.dev/search/?q=function+%22hello+world%22"
84+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
85+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSearchSuccess, mock.Anything)
86+
},
87+
ExpectedOutputs: []string{
88+
"Searching Slack developer docs: \"function \"hello world\"\"",
89+
"https://docs.slack.dev/search/?q=function+%22hello+world%22",
90+
},
91+
},
92+
"handles empty search query as homepage": {
93+
CmdArgs: []string{"--search", ""},
94+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
95+
expectedURL := "https://docs.slack.dev"
96+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
97+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSuccess, mock.Anything)
98+
},
99+
ExpectedOutputs: []string{
100+
"Slack developer docs",
101+
"https://docs.slack.dev",
102+
},
103+
},
104+
"handles the exact user request example": {
105+
CmdArgs: []string{"--search", "something example"},
106+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
107+
expectedURL := "https://docs.slack.dev/search/?q=something+example"
108+
cm.Browser.AssertCalled(t, "OpenURL", expectedURL)
109+
cm.IO.AssertCalled(t, "PrintTrace", mock.Anything, slacktrace.DocsSearchSuccess, mock.Anything)
110+
},
111+
ExpectedOutputs: []string{
112+
"Searching Slack developer docs: \"something example\"",
113+
"https://docs.slack.dev/search/?q=something+example",
114+
},
115+
},
116+
}, func(cf *shared.ClientFactory) *cobra.Command {
117+
return NewCommand(cf)
118+
})
119+
}

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/slackapi/slack-cli/cmd/collaborators"
2828
"github.com/slackapi/slack-cli/cmd/datastore"
2929
"github.com/slackapi/slack-cli/cmd/docgen"
30+
"github.com/slackapi/slack-cli/cmd/docs"
3031
"github.com/slackapi/slack-cli/cmd/doctor"
3132
"github.com/slackapi/slack-cli/cmd/env"
3233
"github.com/slackapi/slack-cli/cmd/externalauth"
@@ -94,6 +95,7 @@ func NewRootCommand(clients *shared.ClientFactory, updateNotification *update.Up
9495
{Command: "init", Meaning: "Initialize an existing Slack app"},
9596
{Command: "run", Meaning: "Start a local development server"},
9697
{Command: "deploy", Meaning: "Deploy to the Slack Platform"},
98+
{Command: "docs", Meaning: "Open Slack developer docs"},
9799
}),
98100
Long: strings.Join([]string{
99101
`{{Emoji "sparkles"}}CLI to create, run, and deploy Slack apps`,
@@ -179,6 +181,7 @@ func Init(ctx context.Context) (*cobra.Command, *shared.ClientFactory) {
179181
rootCmd.CompletionOptions.HiddenDefaultCmd = true
180182

181183
topLevelCommands := []*cobra.Command{
184+
docs.NewCommand(clients),
182185
doctor.NewDoctorCommand(clients),
183186
feedback.NewFeedbackCommand(clients),
184187
}

internal/slacktrace/slacktrace.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const (
7474
DatastoreCountDatastore = "SLACK_TRACE_DATASTORE_COUNT_DATASTORE"
7575
DatastoreCountSuccess = "SLACK_TRACE_DATASTORE_COUNT_SUCCESS"
7676
DatastoreCountTotal = "SLACK_TRACE_DATASTORE_COUNT_TOTAL"
77+
DocsSearchSuccess = "SLACK_TRACE_DOCS_SEARCH_SUCCESS"
78+
DocsSuccess = "SLACK_TRACE_DOCS_SUCCESS"
7779
EnvAddSuccess = "SLACK_TRACE_ENV_ADD_SUCCESS"
7880
EnvListCount = "SLACK_TRACE_ENV_LIST_COUNT"
7981
EnvListVariables = "SLACK_TRACE_ENV_LIST_VARIABLES"

0 commit comments

Comments
 (0)