Skip to content

Commit

Permalink
feat: use SDK metadata (#378)
Browse files Browse the repository at this point in the history
* feat: use SDK metadata

---------

Co-authored-by: Danny Olson <dolson@launchdarkly.com>
  • Loading branch information
cwaldren-ld and dbolson authored Aug 12, 2024
1 parent 29d8f40 commit b0e03ca
Show file tree
Hide file tree
Showing 45 changed files with 4,327 additions and 435 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/iancoleman/strcase v0.3.0
github.com/launchdarkly/api-client-go/v14 v14.0.0
github.com/launchdarkly/sdk-meta/api v0.1.1
github.com/mitchellh/go-homedir v1.1.0
github.com/muesli/reflow v0.3.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
Expand All @@ -19,6 +20,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/term v0.18.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -69,9 +71,8 @@ require (
github.com/yuin/goldmark-emoji v1.0.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/launchdarkly/api-client-go/v14 v14.0.0 h1:fZfi5zKwgjpaOgK4NKcU5mJT2C8sYsR8nnuJYTaFvNU=
github.com/launchdarkly/api-client-go/v14 v14.0.0/go.mod h1:K7ejD5nn9ar94p/5qrQ0t9iJygdIQyH70U9M9rYvw5Y=
github.com/launchdarkly/sdk-meta/api v0.1.1 h1:B9UaOFTDGQSDzbSTwqiBmaTN3xDfrlIu1JQ9LCs2UiA=
github.com/launchdarkly/sdk-meta/api v0.1.1/go.mod h1:vXfR0z4XBz49IYT/2GDEza+Iat3PcuBCC438AZT6oDg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
Expand Down Expand Up @@ -345,8 +347,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
243 changes: 97 additions & 146 deletions internal/quickstart/choose_sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ package quickstart
import (
"fmt"
"io"
"io/fs"
"path/filepath"
"strings"

"github.com/launchdarkly/ldcli/internal/sdks"
"github.com/launchdarkly/sdk-meta/api/sdkmeta"
"golang.org/x/exp/slices"

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
Expand All @@ -18,23 +24,18 @@ var (
titleBarStyle = lipgloss.NewStyle().MarginBottom(1)
)

const (
clientSideSDK = "client"
mobileSDK = "mobile"
serverSideSDK = "server"
)

type chooseSDKModel struct {
help help.Model
helpKeys keyMap
list list.Model
sdkDetails []sdkDetail
selectedIndex int
selectedSDK sdkDetail
}

func NewChooseSDKModel(selectedIndex int) tea.Model {
initSDKs()
l := list.New(sdksToItems(), sdkDelegate{}, 30, 9)
sdkDetails := initSDKs()
l := list.New(sdksToItems(sdkDetails), sdkDelegate{}, 30, 9)
l.FilterInput.PromptStyle = lipgloss.NewStyle()

l.Title = "Select your SDK:"
Expand All @@ -61,15 +62,90 @@ func NewChooseSDKModel(selectedIndex int) tea.Model {
Quit: BindingQuit,
},
list: l,
sdkDetails: sdkDetails,
selectedIndex: selectedIndex,
}
}

// initSDKs sets the index of each SDK based on place in list.
func initSDKs() {
for i := range SDKs {
SDKs[i].index = i
// The CLI uses the sdkmeta project to obtain metadata about each SDK, including the display names
// and types (client, server, etc.)
// Currently, there is no sdkmeta for code examples associated with each SDK, so we hard-code the examples here.
// Once they are part of sdkmeta we can remove this list.
var sdkExamples = map[string]string{
"react-client-sdk": "https://github.com/launchdarkly/react-client-sdk/tree/main/examples/typescript",
"vue": "https://github.com/launchdarkly/vue-client-sdk/tree/main/example",
"react-native": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native/example",
"cpp-client-sdk": "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-client",
"cpp-server-sdk": "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-server",
"lua-server-server": "https://github.com/launchdarkly/lua-server-sdk/tree/main/examples/hello-lua-server",
}

// sdkOrder is a list of IDs the SDKs in order that they should be rendered based on popularity.
var sdkOrder = []string{
"react-client-sdk",
"node-server",
"python-server-sdk",
"java-server-sdk",
"dotnet-server-sdk",
"js-client-sdk",
"vue",
"swift-client-sdk",
"go-server-sdk",
"android",
"react-native",
"ruby-server-sdk",
"flutter-client-sdk",
"dotnet-client-sdk",
"erlang-server-sdk",
"rust-server-sdk",
"cpp-client-sdk",
"roku",
"node-client-sdk",
"cpp-server-sdk",
"lua-server-sdk",
"haskell-server-sdk",
"php-server-sdk",
}

// initSDKs is responsible for loading SDK quickstart instructions from the embedded filesystem.
//
// The names of the files are special: they are the ID of the SDK (e.g. react-native), and are used as an index or
// key to lookup associated sdk metadata (display name, SDK type, etc.)
//
// Therefore, take care when naming the files. A list of valid SDK IDs can be found here:
// https://github.com/launchdarkly/sdk-meta/blob/main/products/names.json
func initSDKs() []sdkDetail {
items, err := sdks.InstructionFiles.ReadDir("sdk_instructions")
if err != nil {
panic("failed to load embedded SDK quickstart instructions: " + err.Error())
}

slices.SortFunc(items, func(a fs.DirEntry, b fs.DirEntry) int {
return strings.Compare(a.Name(), b.Name())
})

details := make([]sdkDetail, 0, len(items))
for _, item := range items {
id, _, _ := strings.Cut(filepath.Base(item.Name()), ".")
if _, ok := sdkmeta.Names[id]; !ok {
continue
}
index := slices.Index(sdkOrder, id)
if index == -1 {
// if we missed an SDK don't add it with an invalid index
continue
}

details = append(details, sdkDetail{
id: id,
index: index,
displayName: sdkmeta.Names[id],
sdkType: sdkmeta.Types[id],
url: sdkExamples[id],
})
}

return details
}

// Init sends commands when the model is created that will:
Expand Down Expand Up @@ -116,145 +192,20 @@ func (m chooseSDKModel) View() string {
}

type sdkDetail struct {
canonicalName string
displayName string
index int
kind string
url string // custom URL if it differs from the other SDKs
id string
displayName string
index int
sdkType sdkmeta.Type
url string // custom URL if it differs from the other SDKs
}

func (s sdkDetail) FilterValue() string { return s.displayName }

var SDKs = []sdkDetail{
{
canonicalName: "react",
displayName: "React",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/react-client-sdk/tree/main/examples/typescript",
},
{
canonicalName: "node-server",
displayName: "Node.js (server-side)",
kind: serverSideSDK,
},
{
canonicalName: "python",
displayName: "Python",
kind: serverSideSDK,
},
{
canonicalName: "java",
displayName: "Java",
kind: serverSideSDK,
},
{
canonicalName: "dotnet-server",
displayName: ".NET (server-side)",
kind: serverSideSDK,
},
{
canonicalName: "js",
displayName: "JavaScript",
kind: clientSideSDK,
},
{
canonicalName: "vue",
displayName: "Vue",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/vue-client-sdk/tree/main/example",
},
{
canonicalName: "ios-swift",
displayName: "iOS",
kind: mobileSDK,
},
{
canonicalName: "go",
displayName: "Go",
kind: serverSideSDK,
},
{
canonicalName: "android",
displayName: "Android",
kind: mobileSDK,
},
{
canonicalName: "react-native",
displayName: "React Native",
kind: mobileSDK,
url: "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native/example",
},
{
canonicalName: "ruby",
displayName: "Ruby",
kind: serverSideSDK,
},
{
canonicalName: "flutter",
displayName: "Flutter",
kind: mobileSDK,
},
{
canonicalName: "dotnet-client",
displayName: ".NET (client-side)",
kind: clientSideSDK,
},
{
canonicalName: "erlang",
displayName: "Erlang",
kind: serverSideSDK,
},
{
canonicalName: "rust",
displayName: "Rust",
kind: serverSideSDK,
},
{
canonicalName: "c-client",
displayName: "C/C++ (client-side)",
kind: clientSideSDK,
url: "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-client",
},
{
canonicalName: "roku",
displayName: "Roku",
kind: clientSideSDK,
},
{
canonicalName: "node-client",
displayName: "Node.js (client-side)",
kind: clientSideSDK,
},
{
canonicalName: "c-server",
displayName: "C/C++ (server-side)",
kind: serverSideSDK,
url: "https://github.com/launchdarkly/cpp-sdks/tree/main/examples/hello-cpp-server",
},
{
canonicalName: "lua-server",
displayName: "Lua",
kind: serverSideSDK,
url: "https://github.com/launchdarkly/lua-server-sdk/tree/main/examples/hello-lua-server",
},
{
canonicalName: "haskell-server",
displayName: "Haskell",
kind: serverSideSDK,
},
{
canonicalName: "php",
displayName: "PHP",
kind: serverSideSDK,
},
}

func sdksToItems() []list.Item {
items := make([]list.Item, len(SDKs))
for i, sdk := range SDKs {
items[i] = list.Item(sdk)
func sdksToItems(sdkDetails []sdkDetail) []list.Item {
items := make([]list.Item, len(sdkDetails))
for _, info := range sdkDetails {
items[info.index] = list.Item(info)
}

return items
}

Expand Down
12 changes: 6 additions & 6 deletions internal/quickstart/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.baseURI,
m.height,
m.width,
m.sdk.canonicalName,
m.sdk.id,
m.sdk.displayName,
m.sdk.url,
m.sdk.kind,
m.sdk.sdkType,
m.flagKey,
m.environment,
)
Expand All @@ -153,10 +153,10 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.baseURI,
m.height,
m.width,
msg.sdk.canonicalName,
msg.sdk.id,
msg.sdk.displayName,
msg.sdk.url,
msg.sdk.kind,
msg.sdk.sdkType,
m.flagKey,
m.environment,
)
Expand Down Expand Up @@ -196,7 +196,7 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.accessToken,
m.baseURI,
m.flagKey,
m.sdk.kind,
m.sdk.id,
)
cmd = m.currentModel.Init()
m.currentStep += 1
Expand All @@ -213,7 +213,7 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmd = tea.Batch(
cmd,
trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String()),
trackSetupSDKSelectedEvent(m.analyticsTracker, m.sdk.canonicalName),
trackSetupSDKSelectedEvent(m.analyticsTracker, m.sdk.id),
)
case m.currentStep == stepToggleFlag && !m.flagToggled:
// the first time we get to the flag toggled view
Expand Down
2 changes: 1 addition & 1 deletion internal/quickstart/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ type choseSDKMsg struct {
func chooseSDK(sdk sdkDetail) tea.Cmd {
return func() tea.Msg {
if sdk.url == "" {
sdk.url = fmt.Sprintf("https://github.com/launchdarkly/hello-%s", sdk.canonicalName)
sdk.url = fmt.Sprintf("https://github.com/launchdarkly/hello-%s", sdk.id)
}

return choseSDKMsg{
Expand Down
Loading

0 comments on commit b0e03ca

Please sign in to comment.