From a4f1db5a87bd7f0cf182f2d448f583acc6843286 Mon Sep 17 00:00:00 2001 From: Ramon Niebla Date: Fri, 1 May 2026 10:04:06 -0700 Subject: [PATCH 1/2] feat!: remove ldcli setup interactive wizard The interactive setup wizard hasn't been touched in ~21 months and ships ~2-year-old pinned SDK versions in its embedded snippets. Removing it along with the bubbletea TUI package, the embedded SDK markdown, and the go.mod entries it was the sole consumer of. BREAKING CHANGE: `ldcli setup` is no longer available. See the README and LaunchDarkly docs for the recommended onboarding path. Co-authored-by: Cursor --- CHANGELOG.md | 1 + README.md | 2 - cmd/quickstart.go | 76 ----- cmd/root.go | 1 - cmd/templates.go | 1 - go.mod | 16 +- go.sum | 21 -- internal/quickstart/choose_sdk.go | 204 ----------- internal/quickstart/container.go | 254 -------------- internal/quickstart/create_flag.go | 112 ------- internal/quickstart/help.go | 114 ------- internal/quickstart/messages.go | 317 ------------------ internal/quickstart/show_sdk_instructions.go | 237 ------------- internal/quickstart/toggle_flag.go | 172 ---------- internal/sdks/sdk_instructions/android.md | 154 --------- .../sdks/sdk_instructions/cpp-client-sdk.md | 128 ------- .../sdks/sdk_instructions/cpp-server-sdk.md | 129 ------- .../sdk_instructions/dotnet-client-sdk.md | 62 ---- .../sdk_instructions/dotnet-server-sdk.md | 114 ------- .../sdk_instructions/erlang-server-sdk.md | 82 ----- .../sdk_instructions/flutter-client-sdk.md | 70 ---- .../sdks/sdk_instructions/go-server-sdk.md | 107 ------ .../sdk_instructions/haskell-server-sdk.md | 109 ------ .../sdks/sdk_instructions/java-server-sdk.md | 170 ---------- .../sdks/sdk_instructions/js-client-sdk.md | 70 ---- .../sdks/sdk_instructions/lua-server-sdk.md | 56 ---- .../sdks/sdk_instructions/node-client-sdk.md | 56 ---- internal/sdks/sdk_instructions/node-server.md | 89 ----- .../sdks/sdk_instructions/php-server-sdk.md | 88 ----- .../sdk_instructions/python-server-sdk.md | 106 ------ .../sdks/sdk_instructions/react-client-sdk.md | 75 ----- .../sdks/sdk_instructions/react-native.md | 77 ----- internal/sdks/sdk_instructions/roku.md | 137 -------- .../sdks/sdk_instructions/ruby-server-sdk.md | 90 ----- .../sdks/sdk_instructions/rust-server-sdk.md | 101 ------ .../sdks/sdk_instructions/swift-client-sdk.md | 93 ----- internal/sdks/sdk_instructions/vue.md | 50 --- internal/sdks/sdks.go | 56 ---- internal/sdks/sdks_test.go | 77 ----- 39 files changed, 5 insertions(+), 3869 deletions(-) delete mode 100644 cmd/quickstart.go delete mode 100644 internal/quickstart/choose_sdk.go delete mode 100644 internal/quickstart/container.go delete mode 100644 internal/quickstart/create_flag.go delete mode 100644 internal/quickstart/help.go delete mode 100644 internal/quickstart/messages.go delete mode 100644 internal/quickstart/show_sdk_instructions.go delete mode 100644 internal/quickstart/toggle_flag.go delete mode 100644 internal/sdks/sdk_instructions/android.md delete mode 100644 internal/sdks/sdk_instructions/cpp-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/cpp-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/dotnet-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/dotnet-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/erlang-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/flutter-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/go-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/haskell-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/java-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/js-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/lua-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/node-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/node-server.md delete mode 100644 internal/sdks/sdk_instructions/php-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/python-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/react-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/react-native.md delete mode 100644 internal/sdks/sdk_instructions/roku.md delete mode 100644 internal/sdks/sdk_instructions/ruby-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/rust-server-sdk.md delete mode 100644 internal/sdks/sdk_instructions/swift-client-sdk.md delete mode 100644 internal/sdks/sdk_instructions/vue.md delete mode 100644 internal/sdks/sdks.go delete mode 100644 internal/sdks/sdks_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 405d35c0..b3f5a684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ ### ⚠ BREAKING CHANGES +* The interactive `ldcli setup` onboarding wizard has been removed. Use the LaunchDarkly docs at https://docs.launchdarkly.com/sdk for SDK installation guidance, and use `ldcli flags create` to create flags and `ldcli flags toggle-on` / `ldcli flags toggle-off` to toggle them from the CLI. * When stdout is not a TTY, the default `--output` format is now **json** instead of plaintext. Scripts that assumed plaintext when output was piped or redirected should set `LD_OUTPUT=plaintext`, run `ldcli config --set output plaintext`, or pass `--output plaintext` (or `--output json` explicitly if you want JSON regardless of TTY). You can also set **`FORCE_TTY`** or **`LD_FORCE_TTY`** to any non-empty value to keep plaintext as the default when stdout is not a TTY, without changing the saved `output` setting. * Error responses now include `statusCode` (integer) and `suggestion` (string) fields in the JSON body. The `message` field for empty-body errors uses `http.StatusText` casing (e.g., `"Method Not Allowed"` instead of the previous `"method not allowed"`). If you parse error JSON from `ldcli`, update any assertions on the exact shape or casing. * Plaintext list output for `flags`, `projects`, `environments`, `members`, and `segments` now renders as aligned tables instead of `* name (key)` bullets. Singular resources render as key-value blocks. If you parse plaintext output programmatically, switch to `--output json`. diff --git a/README.md b/README.md index 621da7a1..a499726f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ The LaunchDarkly CLI helps you manage your feature flags from your terminal or y With the CLI, you can: -- Create and evaluate your first feature flag with a guided `setup` command. - Onboard your whole team by inviting new members. - Interact with the [LaunchDarkly API](https://apidocs.launchdarkly.com/) using resource- and CRUD-based commands. @@ -104,7 +103,6 @@ Effective output is resolved in this order: **`--json`** (if set, wins over `--o LaunchDarkly CLI commands: -- `setup` guides you through creating your first flag, connecting an SDK, and evaluating your flag in your Test environment - `dev-server` lets you start a local server and retrieve flag values from a LaunchDarkly source environment so you can test your code locally. For assistance starting with or running dev-server, refer to the [reference docs](https://launchdarkly.com/docs/guides/flags/ldcli-dev-server). ### Resource Commands diff --git a/cmd/quickstart.go b/cmd/quickstart.go deleted file mode 100644 index fe40523f..00000000 --- a/cmd/quickstart.go +++ /dev/null @@ -1,76 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "os" - - tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" - "github.com/launchdarkly/ldcli/cmd/cliflags" - "github.com/launchdarkly/ldcli/cmd/validators" - "github.com/launchdarkly/ldcli/internal/analytics" - "github.com/launchdarkly/ldcli/internal/environments" - "github.com/launchdarkly/ldcli/internal/flags" - "github.com/launchdarkly/ldcli/internal/quickstart" -) - -func NewQuickStartCmd( - analyticsTrackerFn analytics.TrackerFn, - environmentsClient environments.Client, - flagsClient flags.Client, -) *cobra.Command { - return &cobra.Command{ - Args: validators.Validate(), - Long: "", - PreRun: func(cmd *cobra.Command, args []string) { - analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "setup", nil)) - }, - RunE: runQuickStart(analyticsTrackerFn, environmentsClient, flagsClient), - Short: "Setup guide to create your first feature flag", - Use: "setup", - } -} - -func runQuickStart( - analyticsTrackerFn analytics.TrackerFn, - environmentsClient environments.Client, - flagsClient flags.Client, -) func(*cobra.Command, []string) error { - return func(cmd *cobra.Command, args []string) error { - f, err := tea.LogToFile("debug.log", "") - if err != nil { - fmt.Println("could not open file for debugging:", err) - os.Exit(1) - } - defer f.Close() - - analyticsTracker := analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ) - _, err = tea.NewProgram( - quickstart.NewContainerModel( - analyticsTracker, - environmentsClient, - flagsClient, - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - ), - tea.WithAltScreen(), - ).Run() - if err != nil { - log.Fatal(err) - } - - return nil - } -} diff --git a/cmd/root.go b/cmd/root.go index d498e07e..c6dcc236 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -251,7 +251,6 @@ func NewRootCommand( configCmd := configcmd.NewConfigCmd(configService, analyticsTrackerFn) cmd.AddCommand(configCmd.Cmd()) - cmd.AddCommand(NewQuickStartCmd(analyticsTrackerFn, clients.EnvironmentsClient, clients.FlagsClient)) cmd.AddCommand(logincmd.NewLoginCmd(clients.ResourcesClient)) cmd.AddCommand(signupcmd.NewSignupCmd(analyticsTrackerFn)) cmd.AddCommand(resourcecmd.NewResourcesCmd()) diff --git a/cmd/templates.go b/cmd/templates.go index 3a96b533..82230be9 100644 --- a/cmd/templates.go +++ b/cmd/templates.go @@ -11,7 +11,6 @@ func getUsageTemplate() string { {{.CommandPath}} [command] Commands: - {{rpad "setup" 29}} Create your first feature flag using a step-by-step guide {{rpad "config" 29}} View and modify specific configuration values {{rpad "completion" 29}} Enable command autocompletion within supported shells {{rpad "login" 29}} Log in to your LaunchDarkly account diff --git a/go.mod b/go.mod index 17601e5e..1e20af86 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,7 @@ go 1.23.0 require ( github.com/adrg/xdg v0.5.3 - github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/glamour v0.10.0 - github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/getkin/kin-openapi v0.127.0 github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 @@ -16,10 +13,8 @@ require ( github.com/launchdarkly/api-client-go/v14 v14.0.0 github.com/launchdarkly/go-sdk-common/v3 v3.4.0 github.com/launchdarkly/go-server-sdk/v7 v7.13.4 - github.com/launchdarkly/sdk-meta/api v0.4.8 github.com/mattn/go-sqlite3 v1.14.28 github.com/mitchellh/go-homedir v1.1.0 - github.com/muesli/reflow v0.3.0 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 github.com/oapi-codegen/runtime v1.1.2 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c @@ -30,7 +25,6 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 go.uber.org/mock v0.5.2 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/term v0.33.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -38,18 +32,18 @@ require ( require ( github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -69,12 +63,10 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -82,7 +74,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/spf13/afero v1.15.0 // indirect @@ -94,6 +85,7 @@ require ( github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect diff --git a/go.sum b/go.sum index 067b0ebb..8acd9607 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,6 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= @@ -54,10 +52,6 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= -github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= -github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= @@ -93,8 +87,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -206,8 +198,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -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/ccache v1.1.0 h1:voD1M+ZJXR3MREOKtBwgTF9hYHl1jg+vFKS/+VAkR2k= @@ -228,16 +218,12 @@ github.com/launchdarkly/go-server-sdk/v7 v7.13.4 h1:Jn4HQDkmV0DhbUKLz7gFbNrhVrE3 github.com/launchdarkly/go-server-sdk/v7 v7.13.4/go.mod h1:EEUSX/bc1mVq+3pwrRzTfu8LFRWRI1UL4XMgzsKWmbE= github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg= github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo= -github.com/launchdarkly/sdk-meta/api v0.4.8 h1:PAfhLfoozyQM04AzN7vxzQUc5mrINiwgk3gjbUMZhzY= -github.com/launchdarkly/sdk-meta/api v0.4.8/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -249,10 +235,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= @@ -301,8 +283,6 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -485,7 +465,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/quickstart/choose_sdk.go b/internal/quickstart/choose_sdk.go deleted file mode 100644 index 93fcc177..00000000 --- a/internal/quickstart/choose_sdk.go +++ /dev/null @@ -1,204 +0,0 @@ -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" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -var ( - sdkStyle = lipgloss.NewStyle().PaddingLeft(4) - selectedSdkItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) - titleBarStyle = lipgloss.NewStyle().MarginBottom(1) -) - -type chooseSDKModel struct { - help help.Model - helpKeys keyMap - list list.Model - sdkDetails []sdkDetail - selectedIndex int - selectedSDK sdkDetail -} - -func NewChooseSDKModel(selectedIndex int) tea.Model { - sdkDetails := initSDKs() - l := list.New(sdksToItems(sdkDetails), sdkDelegate{}, 30, 9) - l.FilterInput.PromptStyle = lipgloss.NewStyle() - - l.Title = "Select your SDK:" - // reset title styles - l.Styles.Title = lipgloss.NewStyle() - l.Styles.TitleBar = titleBarStyle - l.SetShowHelp(false) - l.SetShowPagination(true) - l.SetShowStatusBar(false) - - return chooseSDKModel{ - help: help.New(), - helpKeys: keyMap{ - Back: BindingBack, - Filter: BindingFilter, - CursorUp: BindingCursorUp, - CursorDown: BindingCursorDown, - PrevPage: BindingPrevPage, - NextPage: BindingNextPage, - GoToStart: BindingGoToStart, - GoToEnd: BindingGoToEnd, - ShowFullHelp: BindingShowFullHelp, - CloseFullHelp: BindingCloseFullHelp, - Quit: BindingQuit, - }, - list: l, - sdkDetails: sdkDetails, - selectedIndex: selectedIndex, - } -} - -// 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", -} - -// 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 - } - popularity, ok := sdkmeta.Popularity[id] - if !ok { - // if we missed an SDK don't add it with an invalid index - continue - } - - details = append(details, sdkDetail{ - id: id, - index: popularity - 1, // subtract one since popularity is one-indexed - displayName: sdkmeta.Names[id], - sdkType: sdkmeta.Types[id], - url: sdkExamples[id], - }) - } - - return details -} - -// Init sends commands when the model is created that will: -// * select an SDK if it's already been selected -func (m chooseSDKModel) Init() tea.Cmd { - return selectedSDK(m.selectedIndex) -} - -func (m chooseSDKModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case msg.String() == "/": - if m.list.FilterState() == list.Filtering { - m.list.SetFilteringEnabled(false) - } else { - m.list.SetFilteringEnabled(true) - } - m.list, cmd = m.list.Update(msg) - case key.Matches(msg, pressableKeys.Enter): - i, ok := m.list.SelectedItem().(sdkDetail) - if ok { - m.selectedSDK = i - cmd = chooseSDK(m.selectedSDK) - } - case key.Matches(msg, m.helpKeys.CloseFullHelp): - m.help.ShowAll = !m.help.ShowAll - default: - m.list, cmd = m.list.Update(msg) - } - case selectedSDKMsg: - m.list.Select(msg.index) - default: - // update list from list.FilterMatchesMsg - m.list, cmd = m.list.Update(msg) - } - - return m, cmd -} - -func (m chooseSDKModel) View() string { - return m.list.View() + footerView(m.help.View(m.helpKeys), nil) -} - -type sdkDetail struct { - 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 } - -func sdksToItems(sdkDetails []sdkDetail) []list.Item { - items := make([]list.Item, len(sdkDetails)) - for _, info := range sdkDetails { - items[info.index] = list.Item(info) - } - return items -} - -type sdkDelegate struct{} - -func (d sdkDelegate) Height() int { return 1 } -func (d sdkDelegate) Spacing() int { return 0 } -func (d sdkDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } -func (d sdkDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { - i, ok := listItem.(sdkDetail) - if !ok { - return - } - - fn := sdkStyle.Render - if index == m.Index() { - fn = func(s ...string) string { - return selectedSdkItemStyle.Render("> " + strings.Join(s, " ")) - } - } - - fmt.Fprint(w, fn(fmt.Sprintf("%d. %s", index+1, i.displayName))) -} diff --git a/internal/quickstart/container.go b/internal/quickstart/container.go deleted file mode 100644 index d299327d..00000000 --- a/internal/quickstart/container.go +++ /dev/null @@ -1,254 +0,0 @@ -package quickstart - -import ( - "fmt" - "time" - - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/muesli/reflow/wordwrap" - - "github.com/launchdarkly/ldcli/internal/analytics" - "github.com/launchdarkly/ldcli/internal/environments" - "github.com/launchdarkly/ldcli/internal/flags" -) - -const ( - defaultProjKey = "default" - defaultEnvKey = "test" -) - -type step int - -const ( - _ step = iota - stepCreateFlag - stepChooseSDK - stepShowSDKInstructions - stepToggleFlag -) - -func (s step) String() string { - return []string{ - "_", - "1 - feature flag name", - "2 - sdk selection", - "3 - sdk installation", - "4 - flag toggle", - }[s] -} - -// ContainerModel is a high level container model that controls the nested models where each -// represents a step in the quick-start flow. -type ContainerModel struct { - accessToken string - analyticsTracker analytics.Tracker - baseURI string - currentModel tea.Model - currentStep step - environment *environment - environmentsClient environments.Client - err error - flagKey string - flagStatus bool - flagToggled bool - flagsClient flags.Client - gettingStarted bool - height int - quitting bool - sdk sdkDetail - startTime time.Time - toggleCount int - toggleTime time.Time - totalSteps int - width int -} - -func NewContainerModel( - analyticsTracker analytics.Tracker, - environmentsClient environments.Client, - flagsClient flags.Client, - accessToken string, - baseURI string, -) tea.Model { - return ContainerModel{ - accessToken: accessToken, - analyticsTracker: analyticsTracker, - baseURI: baseURI, - currentModel: NewCreateFlagModel(flagsClient, accessToken, baseURI), - currentStep: 1, - environmentsClient: environmentsClient, - flagsClient: flagsClient, - gettingStarted: true, - startTime: time.Now(), - totalSteps: 4, - } -} - -func (m ContainerModel) Init() tea.Cmd { - return trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String()) -} - -func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - var shouldSendTrackingEvent bool - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.height = msg.Height - m.width = msg.Width - m.currentModel, cmd = m.currentModel.Update(msg) - case tea.KeyMsg: - switch { - case key.Matches(msg, pressableKeys.Quit): - m.quitting = true - cmd = tea.Quit - case key.Matches(msg, pressableKeys.Back): - switch m.currentStep { - case stepCreateFlag: - // can only go back if a flag has been created but not confirmed, - // so let the model handle the Update - m.currentModel, cmd = m.currentModel.Update(msg) - case stepChooseSDK: - m.currentStep -= 1 - m.currentModel = NewCreateFlagModel(m.flagsClient, m.accessToken, m.baseURI) - case stepShowSDKInstructions: - m.currentStep -= 1 - m.currentModel = NewChooseSDKModel(m.sdk.index) - cmd = m.currentModel.Init() - case stepToggleFlag: - m.currentStep -= 1 - m.currentModel = NewShowSDKInstructionsModel( - m.environmentsClient, - m.flagsClient, - m.accessToken, - m.baseURI, - m.height, - m.width, - m.sdk.id, - m.sdk.displayName, - m.sdk.url, - m.sdk.sdkType, - m.flagKey, - m.environment, - ) - cmd = m.currentModel.Init() - shouldSendTrackingEvent = true - } - default: - // delegate all other input to the current model - m.currentModel, cmd = m.currentModel.Update(msg) - } - case confirmedFlagMsg: - m.currentModel = NewChooseSDKModel(0) - m.flagKey = msg.flag.key - m.currentStep += 1 - m.err = nil - shouldSendTrackingEvent = true - case choseSDKMsg: - m.currentModel = NewShowSDKInstructionsModel( - m.environmentsClient, - m.flagsClient, - m.accessToken, - m.baseURI, - m.height, - m.width, - msg.sdk.id, - msg.sdk.displayName, - msg.sdk.url, - msg.sdk.sdkType, - m.flagKey, - m.environment, - ) - cmd = m.currentModel.Init() - m.sdk = msg.sdk - m.currentStep += 1 - shouldSendTrackingEvent = true - case errMsg: - m.currentModel, cmd = m.currentModel.Update(msg) - case fetchedEnvMsg: - m.environment = &msg.environment - m.currentModel, cmd = m.currentModel.Update(msg) - m.err = nil - case fetchedFlagStatusMsg: - m.currentModel, cmd = m.currentModel.Update(msg) - m.err = nil - case createdFlagMsg, - fetchedSDKInstructionsMsg, - flagToggleThrottleMsg, - selectedSDKMsg, - spinner.TickMsg: - m.gettingStarted = false - m.currentModel, cmd = m.currentModel.Update(msg) - m.err = nil - case toggledFlagMsg: - m.gettingStarted = false - m.currentModel, cmd = m.currentModel.Update(msg) - m.toggleCount++ - m.flagStatus = msg.on - m.toggleTime = msg.time - m.flagToggled = true - m.err = nil - shouldSendTrackingEvent = true - case showToggleFlagMsg: - m.currentModel = NewToggleFlagModel( - m.flagsClient, - m.accessToken, - m.baseURI, - m.flagKey, - m.sdk.id, - ) - cmd = m.currentModel.Init() - m.currentStep += 1 - shouldSendTrackingEvent = true - default: - // delegate other messages - m.currentModel, cmd = m.currentModel.Update(msg) - m.err = nil - } - - if shouldSendTrackingEvent { - switch { - case m.currentStep == stepShowSDKInstructions: - cmd = tea.Batch( - cmd, - trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String()), - trackSetupSDKSelectedEvent(m.analyticsTracker, m.sdk.id), - ) - case m.currentStep == stepToggleFlag && !m.flagToggled: - // the first time we get to the flag toggled view - cmd = tea.Batch( - cmd, - trackSendCommandCompletedEvent(m.analyticsTracker), - trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String()), - ) - case m.currentStep == stepToggleFlag && m.flagToggled: - // subsequent times we've toggled the flag in the toggle flag view - cmd = tea.Batch(cmd, trackSetupFlagToggledEvent( - m.analyticsTracker, - m.flagStatus, - m.toggleCount, - m.toggleTime.Sub(m.startTime).Milliseconds(), - )) - default: - // all other views - cmd = tea.Batch(cmd, trackSetupStepStartedEvent(m.analyticsTracker, m.currentStep.String())) - } - } - - return m, cmd -} - -func (m ContainerModel) View() string { - out := fmt.Sprintf("Step %d of %d\n"+m.currentModel.View(), m.currentStep, m.totalSteps) - - if m.quitting { - return "" - } - - if m.gettingStarted { - out = "Within this guided setup flow, you'll be creating a new feature flag and,\nusing the SDK of your choice, building a small sample application to see a\nfeature flag toggle on and off in real time.\n\nLet's get started!\n\n" + out - } - - return wordwrap.String(out, m.width) -} diff --git a/internal/quickstart/create_flag.go b/internal/quickstart/create_flag.go deleted file mode 100644 index 711b7885..00000000 --- a/internal/quickstart/create_flag.go +++ /dev/null @@ -1,112 +0,0 @@ -package quickstart - -import ( - "fmt" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - - "github.com/launchdarkly/ldcli/internal/flags" -) - -const defaultFlagName = "My New Flag" - -type flag struct { - key string - name string -} - -type createFlagModel struct { - accessToken string - baseUri string - client flags.Client - err error - existingFlagUsed bool - flag flag - help help.Model - helpKeys keyMap - showSuccessView bool - textInput textinput.Model -} - -func NewCreateFlagModel(client flags.Client, accessToken, baseUri string) tea.Model { - ti := textinput.New() - ti.Focus() - ti.CharLimit = 156 - ti.Width = 20 - ti.Prompt = "" - - return createFlagModel{ - accessToken: accessToken, - baseUri: baseUri, - client: client, - help: help.New(), - helpKeys: keyMap{ - Quit: BindingQuit, - }, - textInput: ti, - } -} - -func (m createFlagModel) Init() tea.Cmd { - return nil -} - -func (m createFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, pressableKeys.Enter): - if m.showSuccessView { - return m, confirmedFlag(m.flag) - } - - input := m.textInput.Value() - if input == "" { - input = defaultFlagName - } - flagKey, err := flags.NewKeyFromName(input) - if err != nil { - return m, sendErrMsg(err) - } - - return m, createFlag(m.client, m.accessToken, m.baseUri, input, flagKey, defaultProjKey) - case key.Matches(msg, pressableKeys.Back): - if m.showSuccessView { - m.showSuccessView = false - } - default: - m.textInput, cmd = m.textInput.Update(msg) - } - case createdFlagMsg: - m.showSuccessView = true - m.flag = msg.flag - m.existingFlagUsed = msg.existingFlag - case errMsg: - m.err = msg.err - } - - return m, cmd -} - -func (m createFlagModel) View() string { - style := lipgloss.NewStyle(). - MarginLeft(1) - - if m.showSuccessView { - successMessage := fmt.Sprintf("Flag %q created successfully!", m.flag.name) - if m.existingFlagUsed { - successMessage = fmt.Sprintf("Using existing flag %q for setup.", m.flag.name) - } - return successMessage + " Press enter to continue." - } - - return fmt.Sprintf( - "Name your first feature flag (enter for default value):%s", - style.Render(m.textInput.View()), - ) + footerView(m.help.View(m.helpKeys), m.err) -} diff --git a/internal/quickstart/help.go b/internal/quickstart/help.go deleted file mode 100644 index 5cccf58f..00000000 --- a/internal/quickstart/help.go +++ /dev/null @@ -1,114 +0,0 @@ -package quickstart - -import ( - "github.com/charmbracelet/bubbles/key" -) - -var ( - BindingBack = key.NewBinding( - key.WithKeys("esc"), - key.WithHelp("esc", "back"), - ) - BindingCursorUp = key.NewBinding( - key.WithKeys("up", "k"), - key.WithHelp("↑/k", "up"), - ) - BindingCursorDown = key.NewBinding( - key.WithKeys("down", "j"), - key.WithHelp("↓/j", "down"), - ) - BindingPrevPage = key.NewBinding( - key.WithKeys("left", "h", "pgup", "b", "u"), - key.WithHelp("←/h/pgup", "prev page"), - ) - BindingNextPage = key.NewBinding( - key.WithKeys("right", "l", "pgdown", "f", "d"), - key.WithHelp("→/l/pgdn", "next page"), - ) - BindingGoToStart = key.NewBinding( - key.WithKeys("home", "g"), - key.WithHelp("g/home", "go to start"), - ) - BindingGoToEnd = key.NewBinding( - key.WithKeys("end", "G"), - key.WithHelp("G/end", "go to end"), - ) - BindingShowFullHelp = key.NewBinding( - key.WithKeys("?"), - key.WithHelp("?", "more"), - ) - BindingCloseFullHelp = key.NewBinding( - key.WithKeys("?"), - key.WithHelp("?", "close help"), - ) - BindingQuit = key.NewBinding( - key.WithKeys("ctrl+c"), - key.WithHelp("ctrl+c", "quit"), - ) - BindingFilter = key.NewBinding( - key.WithKeys("/"), - key.WithHelp("/", "filter"), - ) -) - -// keyMap defines all the possible key presses we would respond to -type keyMap struct { - Back key.Binding - CloseFullHelp key.Binding - CursorDown key.Binding - CursorUp key.Binding - Enter key.Binding - Filter key.Binding - GoToEnd key.Binding - GoToStart key.Binding - NextPage key.Binding - PrevPage key.Binding - Quit key.Binding - ShowFullHelp key.Binding - Tab key.Binding -} - -func (k keyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{ - {k.CursorUp, k.CursorDown, k.PrevPage, k.NextPage}, - {k.Back, k.Quit, k.CloseFullHelp}, - } -} - -func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Back, k.Filter, k.Quit, k.ShowFullHelp} -} - -// pressableKeys are the possible key presses we support for all steps. -// We don't necessarily want to show these in the help text, but we want to handle them when -// pressed. -var pressableKeys = keyMap{ - Back: key.NewBinding( - key.WithKeys("esc"), - key.WithHelp("esc", "go back"), - ), - Enter: key.NewBinding( - key.WithKeys("enter"), - key.WithHelp("enter", "select"), - ), - Quit: key.NewBinding( - key.WithKeys("ctrl+c"), - key.WithHelp("ctrl+c", "quit"), - ), - Tab: key.NewBinding( - key.WithKeys("tab"), - key.WithHelp("tab", "toggle"), - ), -} - -// footerView shows any error messages and help text. -func footerView(helpView string, err error) string { - var errView string - spacer := "\n\n\n" - if err != nil { - spacer = "\n\n" - errView = "\n" + err.Error() - } - - return errView + spacer + helpView -} diff --git a/internal/quickstart/messages.go b/internal/quickstart/messages.go deleted file mode 100644 index 1443d697..00000000 --- a/internal/quickstart/messages.go +++ /dev/null @@ -1,317 +0,0 @@ -package quickstart - -import ( - "context" - "encoding/json" - "fmt" - "time" - - tea "github.com/charmbracelet/bubbletea" - - "github.com/launchdarkly/ldcli/internal/analytics" - "github.com/launchdarkly/ldcli/internal/environments" - "github.com/launchdarkly/ldcli/internal/errors" - "github.com/launchdarkly/ldcli/internal/flags" - "github.com/launchdarkly/ldcli/internal/sdks" -) - -// errMsg is sent when there is an error in one of the steps that the container model needs to -// know about. -type errMsg struct { - err error -} - -func sendErrMsg(err error) tea.Cmd { - return func() tea.Msg { - return errMsg{err: err} - } -} - -type toggledFlagMsg struct { - time time.Time - on bool -} - -func toggleFlag(client flags.Client, accessToken, baseUri, flagKey string, enabled bool) tea.Cmd { - return func() tea.Msg { - _, err := client.Update( - context.Background(), - accessToken, - baseUri, - flagKey, - defaultProjKey, - flags.BuildToggleFlagPatch(defaultEnvKey, enabled), - ) - if err != nil { - return errMsg{err: err} - } - - return toggledFlagMsg{ - time: time.Now(), - on: enabled, - } - } -} - -type createdFlagMsg struct { - flag flag - existingFlag bool -} - -type confirmedFlagMsg struct { - flag flag -} - -func confirmedFlag(flag flag) tea.Cmd { - return func() tea.Msg { - return confirmedFlagMsg{flag} - } -} - -type msgRequestError struct { - code string - message string -} - -func newMsgRequestError(errStr string) (msgRequestError, error) { - var e struct { - Code string `json:"code"` - Message string `json:"message"` - } - err := json.Unmarshal([]byte(errStr), &e) - if err != nil { - return msgRequestError{}, err - } - - return msgRequestError{ - code: e.Code, - message: e.Message, - }, nil -} - -func (e msgRequestError) Error() string { - return e.message -} - -func (e msgRequestError) IsConflict() bool { - return e.code == "conflict" -} - -func createFlag(client flags.Client, accessToken, baseUri, flagName, flagKey, projKey string) tea.Cmd { - return func() tea.Msg { - var existingFlag bool - _, err := client.Create( - context.Background(), - accessToken, - baseUri, - flagName, - flagKey, - projKey, - ) - if err != nil { - msgRequestErr, err := newMsgRequestError(err.Error()) - if err != nil { - return errMsg{err: err} - } - - if !msgRequestErr.IsConflict() { - return errMsg{ - err: errors.NewError( - fmt.Sprintf( - "Error creating flag: %s. Press \"ctrl + c\" to quit.", - msgRequestErr.message, - ), - ), - } - } else { - existingFlag = true - } - } - - return createdFlagMsg{flag: flag{ - key: flagKey, - name: flagName, - }, existingFlag: existingFlag} - } -} - -type fetchedSDKInstructionsMsg struct { - instructions []byte -} - -type choseSDKMsg struct { - sdk sdkDetail -} - -func chooseSDK(sdk sdkDetail) tea.Cmd { - return func() tea.Msg { - if sdk.url == "" { - sdk.url = fmt.Sprintf("https://github.com/launchdarkly/hello-%s", sdk.id) - } - - return choseSDKMsg{ - sdk: sdk, - } - } -} - -func readSDKInstructions(filename string) tea.Cmd { - return func() tea.Msg { - content, err := sdks.InstructionFiles.ReadFile(fmt.Sprintf("sdk_instructions/%s.md", filename)) - if err != nil { - return errMsg{err: err} - } - - return fetchedSDKInstructionsMsg{instructions: content} - } -} - -type showToggleFlagMsg struct{} - -func showToggleFlag() tea.Cmd { - return func() tea.Msg { - return showToggleFlagMsg{} - } -} - -type fetchedEnvMsg struct { - environment environment -} - -func fetchEnv( - client environments.Client, - accessToken string, - baseUri string, - key string, - projKey string, -) tea.Cmd { - return func() tea.Msg { - response, err := client.Get(context.Background(), accessToken, baseUri, key, projKey) - if err != nil { - return errMsg{err: err} - } - - var resp struct { - SDKKey string `json:"apiKey"` - ClientSideId string `json:"_id"` - MobileKey string `json:"mobileKey"` - } - err = json.Unmarshal(response, &resp) - if err != nil { - return errMsg{err: err} - } - - return fetchedEnvMsg{environment: environment{ - sdkKey: resp.SDKKey, - mobileKey: resp.MobileKey, - clientSideId: resp.ClientSideId, - }} - - } -} - -type clientSideFlagMsg struct{} // todo: rename - -func updateClientSideFlag( - client flags.Client, - accessToken string, - baseUri string, - key string, -) tea.Cmd { - return func() tea.Msg { - _, err := client.Update( - context.Background(), - accessToken, - baseUri, - key, - defaultProjKey, - []flags.UpdateInput{{Op: "replace", Path: "/clientSideAvailability/usingEnvironmentId", Value: true}}, - ) - if err != nil { - return errMsg{err: err} - } - - return clientSideFlagMsg{} - } -} - -type fetchedFlagStatusMsg struct { - enabled bool -} - -func fetchFlagStatus( - client flags.Client, - accessToken string, - baseUri string, - key, - envKey, - projKey string, -) tea.Cmd { - return func() tea.Msg { - response, err := client.Get(context.Background(), accessToken, baseUri, key, projKey, envKey) - if err != nil { - return errMsg{err: err} - } - - var resp struct { - Environments map[string]interface{} `json:"environments"` - } - err = json.Unmarshal(response, &resp) - if err != nil { - return errMsg{err: err} - } - return fetchedFlagStatusMsg{enabled: resp.Environments[envKey].(map[string]interface{})["on"].(bool)} - } -} - -type selectedSDKMsg struct { - index int -} - -func selectedSDK(index int) tea.Cmd { - return func() tea.Msg { - return selectedSDKMsg{index: index} - } -} - -type eventTrackedMsg struct{} - -func trackSetupStepStartedEvent(tracker analytics.Tracker, step string) tea.Cmd { - return func() tea.Msg { - tracker.SendSetupStepStartedEvent(step) - - return eventTrackedMsg{} - } -} - -func trackSetupSDKSelectedEvent(tracker analytics.Tracker, sdk string) tea.Cmd { - return func() tea.Msg { - tracker.SendSetupSDKSelectedEvent(sdk) - - return eventTrackedMsg{} - } -} - -func trackSetupFlagToggledEvent(tracker analytics.Tracker, on bool, count int, duration_ms int64) tea.Cmd { - return func() tea.Msg { - tracker.SendSetupFlagToggledEvent(on, count, duration_ms) - - return eventTrackedMsg{} - } -} - -func trackSendCommandCompletedEvent(tracker analytics.Tracker) tea.Cmd { - return func() tea.Msg { - tracker.SendCommandCompletedEvent(analytics.SUCCESS) - - return eventTrackedMsg{} - } -} - -type flagToggleThrottleMsg int - -func throttleFlagToggle(count int) tea.Cmd { - return tea.Tick(throttleDuration, func(_ time.Time) tea.Msg { - return flagToggleThrottleMsg(count) - }) -} diff --git a/internal/quickstart/show_sdk_instructions.go b/internal/quickstart/show_sdk_instructions.go deleted file mode 100644 index 0db92bec..00000000 --- a/internal/quickstart/show_sdk_instructions.go +++ /dev/null @@ -1,237 +0,0 @@ -package quickstart - -import ( - "fmt" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/glamour" - "github.com/charmbracelet/lipgloss" - "github.com/launchdarkly/sdk-meta/api/sdkmeta" - - "github.com/launchdarkly/ldcli/internal/environments" - "github.com/launchdarkly/ldcli/internal/flags" - "github.com/launchdarkly/ldcli/internal/sdks" -) - -// stepCountHeight is the approximate height of the current step value shown from the container and -// is used to calculate height of viewport. -const stepCountHeight = 4 - -type environment struct { - sdkKey string - mobileKey string - clientSideId string -} - -type showSDKInstructionsModel struct { - accessToken string - baseUri string - canonicalName string - displayName string - environment *environment - environmentsClient environments.Client - err error - flagsClient flags.Client - flagKey string - help help.Model - helpKeys keyMap - instructions string - sdkKind sdkmeta.Type - spinner spinner.Model - url string - viewport viewport.Model -} - -func NewShowSDKInstructionsModel( - environmentsClient environments.Client, - flagsClient flags.Client, - accessToken string, - baseUri string, - height int, - width int, - canonicalName string, - displayName string, - url string, - sdkKind sdkmeta.Type, - flagKey string, - environment *environment, -) tea.Model { - s := spinner.New() - s.Spinner = spinner.Points - - m := showSDKInstructionsModel{ - accessToken: accessToken, - baseUri: baseUri, - canonicalName: canonicalName, - displayName: displayName, - environmentsClient: environmentsClient, - environment: environment, - flagsClient: flagsClient, - flagKey: flagKey, - help: help.New(), - helpKeys: keyMap{ - Back: BindingBack, - CursorDown: BindingCursorDown, - CursorUp: BindingCursorUp, - Quit: BindingQuit, - }, - sdkKind: sdkKind, - spinner: s, - url: url, - } - - vp := viewport.New( - width, - m.getViewportHeight(height), - ) - - m.viewport = vp - - return m -} - -// Init sends commands when the model is created that will: -// show a spinner while SDK instructions are prepared -// fetch SDK instructions -// fetch the environment to get values to interpolate into the instructions -func (m showSDKInstructionsModel) Init() tea.Cmd { - cmds := []tea.Cmd{m.spinner.Tick, readSDKInstructions(m.canonicalName)} - - if m.environment == nil { - cmds = append(cmds, fetchEnv(m.environmentsClient, m.accessToken, m.baseUri, defaultEnvKey, defaultProjKey)) - } - - if m.sdkKind == sdkmeta.ClientSideType { - cmds = append(cmds, updateClientSideFlag(m.flagsClient, m.accessToken, m.baseUri, m.flagKey)) - } - - return tea.Sequence(cmds...) -} - -func (m showSDKInstructionsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.viewport.Height = m.getViewportHeight(msg.Height) - case tea.KeyMsg: - switch { - case key.Matches(msg, pressableKeys.Enter): - // TODO: only if all data are fetched? - cmd = showToggleFlag() - default: - m.viewport, cmd = m.viewport.Update(msg) - } - case fetchedSDKInstructionsMsg: - m.instructions = string(msg.instructions) - if m.environment != nil { - md, err := m.renderMarkdown() - if err != nil { - return m, sendErrMsg(err) - } - m.viewport.SetContent(m.headerView() + md) - } - case fetchedEnvMsg: - m.environment = &msg.environment - md, err := m.renderMarkdown() - if err != nil { - return m, sendErrMsg(err) - } - m.viewport.SetContent(m.headerView() + md) - case spinner.TickMsg: - m.spinner, cmd = m.spinner.Update(msg) - case errMsg: - m.err = msg.err - } - - return m, cmd -} - -func (m showSDKInstructionsModel) View() string { - if m.err != nil { - return footerView(m.help.View(m.helpKeys), m.err) - } - - if m.instructions == "" || m.environment == nil { - return m.spinner.View() + fmt.Sprintf(" Fetching %s instructions...\n", m.displayName) + footerView(m.help.View(m.helpKeys), nil) - } - - m.help.ShowAll = true - - return m.viewport.View() + m.footerView() -} - -func (m showSDKInstructionsModel) headerView() string { - style := borderStyle().BorderBottom(true) - - return style.Render( - fmt.Sprintf(` -Here are the steps to set up a test app to see feature flagging in action -using the %s in your Default project & Test environment. - -You should have everything you need to get started, including the flag from -the previous step and your environment key from your Test environment already -embedded in the code! - -Open a new terminal window to get started. - -If you want to skip ahead, the final code is available in our GitHub repository: -%s -`, - m.displayName, - m.url, - ), - ) -} - -func (m showSDKInstructionsModel) footerView() string { - // set the width to tbe the same as the header so the borders are the same length - style := borderStyle(). - BorderTop(true). - Width(lipgloss.Width(m.headerView())) - - return style.Render( - "\n(press enter to continue)" + footerView(m.help.View(m.helpKeys), nil), - ) -} - -func (m showSDKInstructionsModel) renderMarkdown() (string, error) { - instructions := sdks.ReplaceFlagKey(m.instructions, m.flagKey) - instructions = sdks.ReplaceSDKKeys( - instructions, - m.environment.sdkKey, - m.environment.clientSideId, - m.environment.mobileKey, - ) - - // set the width to be as long as possible to have less line wrapping in the SDK code - renderer, err := glamour.NewTermRenderer( - glamour.WithAutoStyle(), - glamour.WithWordWrap(m.viewport.Width), - ) - if err != nil { - return "", err - } - - out, err := renderer.Render(instructions) - if err != nil { - return out, err - } - - return out, nil -} - -func (m showSDKInstructionsModel) getViewportHeight(h int) int { - return h - lipgloss.Height(m.footerView()) - stepCountHeight -} - -// borderStyle sets a border for the bottom of the headerView and the top of the footerView to show a -// border around the SDK code. -func borderStyle() lipgloss.Style { - return lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("62")). - BorderStyle(lipgloss.ThickBorder()) -} diff --git a/internal/quickstart/toggle_flag.go b/internal/quickstart/toggle_flag.go deleted file mode 100644 index 34df0441..00000000 --- a/internal/quickstart/toggle_flag.go +++ /dev/null @@ -1,172 +0,0 @@ -package quickstart - -import ( - "fmt" - "time" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/launchdarkly/sdk-meta/api/sdkmeta" - - "github.com/launchdarkly/ldcli/internal/errors" - "github.com/launchdarkly/ldcli/internal/flags" -) - -const ( - resetThrottleCountThreshold = 1 - throttleDuration = time.Second -) - -type toggleFlagModel struct { - accessToken string - baseUri string - client flags.Client - enabled bool - err error - flagKey string - flagWasEnabled bool - flagWasFetched bool - help help.Model - helpKeys keyMap - sdkID string - spinner spinner.Model - - // Throttling fields to control how quickly a user can press (or hold) tab to toggle the flag. - // We publish a message based on the throttleDuration that increments a counter to control when - // we disable the toggle. We also keep track of how many times we've published a command to toggle - // the flag within the throttleDuration timeframe, resetting it once we've stopped throttling. - resetThrottleCount int // incremented when the user toggles the flag which is only allowed when it's below a threshold - throttleCount int // syncs messages to know when the throttleDuration has elapsed - throttling bool // flag to decide when the user is throttled -} - -func NewToggleFlagModel(client flags.Client, accessToken string, baseUri string, flagKey string, sdkID string) tea.Model { - s := spinner.New() - s.Spinner = spinner.Points - - return toggleFlagModel{ - accessToken: accessToken, - baseUri: baseUri, - client: client, - flagKey: flagKey, - help: help.New(), - helpKeys: keyMap{ - Back: BindingBack, - Quit: BindingQuit, - }, - sdkID: sdkID, - spinner: s, - } -} - -func (m toggleFlagModel) Init() tea.Cmd { - cmds := []tea.Cmd{ - m.spinner.Tick, - fetchFlagStatus(m.client, m.accessToken, m.baseUri, m.flagKey, defaultEnvKey, defaultProjKey), - } - - return tea.Sequence(cmds...) -} - -func (m toggleFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, pressableKeys.Quit): - return m, tea.Quit - case key.Matches(msg, pressableKeys.Tab): - if !m.flagWasFetched { - return m, nil - } - if m.throttling || m.resetThrottleCount > resetThrottleCountThreshold { - // don't toggle the flag if we're currently debouncing the user - return m, throttleFlagToggle(m.throttleCount) - } - - m.flagWasEnabled = true - m.enabled = !m.enabled - m.err = nil - m.throttleCount += 1 - cmd = tea.Sequence( - toggleFlag(m.client, m.accessToken, m.baseUri, m.flagKey, m.enabled), - throttleFlagToggle(m.throttleCount), - ) - } - case toggledFlagMsg: - m.resetThrottleCount += 1 - case fetchedFlagStatusMsg: - m.enabled = msg.enabled - m.flagWasFetched = true - case flagToggleThrottleMsg: - // if the value on the model is not the same as the message, we know there will be - // additional messages coming. Once they are equal, we know the throttle time has elapsed. - if int(msg) == m.throttleCount { - m.throttling = false - m.resetThrottleCount = 0 - } else { - m.throttling = true - } - case spinner.TickMsg: - m.spinner, cmd = m.spinner.Update(msg) - case errMsg: - msgRequestErr, err := newMsgRequestError(msg.err.Error()) - if err != nil { - m.err = err - return m, cmd - } - if msgRequestErr.IsConflict() { - m.err = errors.NewError("Error toggling flag: you have toggled too quickly.") - return m, cmd - } - - m.err = msg.err - } - - return m, cmd -} - -func getLogType(sdkID string) string { - switch sdkmeta.Types[sdkID] { - case sdkmeta.ServerSideType, sdkmeta.EdgeType: - return "application logs" - case sdkmeta.ClientSideType: - if sdkID == "js-client-sdk" { - return "browser" - } - return "application" - } - return "logs" -} - -func (m toggleFlagModel) View() string { - var furtherInstructions string - title := "Toggle your feature flag in your Test environment (press tab)" - toggle := "OFF" - bgColor := "#646a73" - margin := 1 - if m.enabled { - bgColor = "#3d9c51" - margin = 2 - toggle = "ON" - } - if !m.flagWasFetched { - margin = 1 - toggle = m.spinner.View() - } - - if m.flagWasEnabled && m.err == nil { - furtherInstructions = fmt.Sprintf("\n\nCheck your %s to see the change!", getLogType(m.sdkID)) - furtherInstructions += "\nDidn't see this change in your application? Check https://docs.launchdarkly.com/home/flags/toggle#watch-for-changes-in-your-application for details." - } - - toggleStyle := lipgloss.NewStyle(). - Background(lipgloss.Color(bgColor)). - Padding(0, 1). - MarginRight(margin) - - return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey + furtherInstructions + footerView(m.help.View(m.helpKeys), m.err) -} diff --git a/internal/sdks/sdk_instructions/android.md b/internal/sdks/sdk_instructions/android.md deleted file mode 100644 index 561f3052..00000000 --- a/internal/sdks/sdk_instructions/android.md +++ /dev/null @@ -1,154 +0,0 @@ -# Installation steps -1. Open Android Studio and create a new project named `hello-android` with an empty activity. - -2. Next, add the LaunchDarkly SDK as a dependency in the `app/build.gradle` file: -```java -dependencies { - ... - implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.2.0") -} -``` - -3. Open the file `MainActivity.kt` and add the following code: -```java -package com.launchdarkly.hello_android - -import android.os.Bundle -import android.view.View -import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import com.launchdarkly.hello_android.MainApplication.Companion.LAUNCHDARKLY_MOBILE_KEY -import com.launchdarkly.sdk.android.LDClient - -class MainActivity : AppCompatActivity() { - - // Set BOOLEAN_FLAG_KEY to the feature flag key you want to evaluate. - val BOOLEAN_FLAG_KEY = "my-flag-key" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - val textView : TextView = findViewById(R.id.textview) - val fullView : View = window.decorView - - if (LAUNCHDARKLY_MOBILE_KEY == "example-mobile-key") { - val builder = AlertDialog.Builder(this) - builder.setMessage("LAUNCHDARKLY_MOBILE_KEY was not customized for this application.") - builder.create().show() - } - - val client = LDClient.get() - val flagValue = client.boolVariation(BOOLEAN_FLAG_KEY, false) - - // to get the variation the SDK has cached - textView.text = getString( - R.string.flag_evaluated, - BOOLEAN_FLAG_KEY, - flagValue.toString() - ) - - // Style the display - textView.setTextColor(resources.getColor(R.color.colorText)) - if(flagValue) { - fullView.setBackgroundColor(resources.getColor(R.color.colorBackgroundTrue)) - } else { - fullView.setBackgroundColor(resources.getColor(R.color.colorBackgroundFalse)) - } - - // to register a listener to get updates in real time - client.registerFeatureFlagListener(BOOLEAN_FLAG_KEY) { - val changedFlagValue = client.boolVariation(BOOLEAN_FLAG_KEY, false) - textView.text = getString( - R.string.flag_evaluated, - BOOLEAN_FLAG_KEY, - changedFlagValue.toString() - ) - if(changedFlagValue) { - fullView.setBackgroundColor(resources.getColor(R.color.colorBackgroundTrue)) - } else { - fullView.setBackgroundColor(resources.getColor(R.color.colorBackgroundFalse)) - } - } - } -} -``` - -4. Add a `TextView` to your `layout/activity_main.xml` with id `textview`: -```xml - -``` - -5. Create `MainApplication.kt` and add the following code: -```java -package com.launchdarkly.hello_android - -import android.app.Application -import com.launchdarkly.sdk.ContextKind -import com.launchdarkly.sdk.LDContext -import com.launchdarkly.sdk.android.LDClient -import com.launchdarkly.sdk.android.LDConfig -import com.launchdarkly.sdk.android.LDConfig.Builder.AutoEnvAttributes - -class MainApplication : Application() { - - companion object { - - // Set LAUNCHDARKLY_MOBILE_KEY to your LaunchDarkly SDK mobile key. - const val LAUNCHDARKLY_MOBILE_KEY = "myMobileKey" - } - - override fun onCreate() { - super.onCreate() - - // Set LAUNCHDARKLY_MOBILE_KEY to your LaunchDarkly mobile key found on the LaunchDarkly - // dashboard in the start guide. - // If you want to disable the Auto EnvironmentAttributes functionality. - // Use AutoEnvAttributes.Disabled as the argument to the Builder - val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled) - .mobileKey(LAUNCHDARKLY_MOBILE_KEY) - .build() - - // Set up the context properties. This context should appear on your LaunchDarkly context - // dashboard soon after you run the demo. - val context = if (isUserLoggedIn()) { - LDContext.builder(ContextKind.DEFAULT, getUserKey()) - .name(getUserName()) - .build() - } else { - LDContext.builder(ContextKind.DEFAULT, "example-user-key") - .anonymous(true) - .build() - } - - LDClient.init(this@MainApplication, ldConfig, context) - } - - private fun isUserLoggedIn(): Boolean = false - - private fun getUserKey(): String = "example-user-key" - - private fun getUserName(): String = "Sandy" -} -``` - -6. Register the `MainApplication` class in the `AndroidManifest.xml`: -```xml - - - - -``` - -Now that your application is ready, run the through the Android Emulator or on a real device to see what value we get. You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/cpp-client-sdk.md b/internal/sdks/sdk_instructions/cpp-client-sdk.md deleted file mode 100644 index 088d4cf6..00000000 --- a/internal/sdks/sdk_instructions/cpp-client-sdk.md +++ /dev/null @@ -1,128 +0,0 @@ -# Installation steps -1. First, ensure the required build dependencies are installed: -- C++ 17 -- CMake 3.19 or higher -- A build system such as make, Ninja, or MSVC -- Boost 1.81 or higher -- OpenSSL - -2. Create a new project directory: -```shell -mkdir hello-cpp-client && cd hello-cpp-client -``` - -3. Clone the C++ SDK inside the directory you created above using git: -```shell -git clone https://github.com/launchdarkly/cpp-sdks.git -``` - -4. Create a file named `main.cpp` and add the following code: -```cpp -#include -#include - -#include -#include - -// Set INIT_TIMEOUT_MILLISECONDS to the amount of time you will wait for -// the client to become initialized. -#define INIT_TIMEOUT_MILLISECONDS 3000 - -using namespace launchdarkly; -using namespace launchdarkly::client_side; - -int main() { - - auto config = ConfigBuilder("myMobileKey").Build(); - if (!config) { - std::cout << "error: config is invalid: " << config.error() << std::endl; - return 1; - } - - auto context = - ContextBuilder().Kind("user", "example-user-key").Name("Sandy").Build(); - - auto client = Client(std::move(*config), std::move(context)); - - auto start_result = client.StartAsync(); - - if (auto const status = start_result.wait_for( - std::chrono::milliseconds(INIT_TIMEOUT_MILLISECONDS)); - status == std::future_status::ready) { - if (start_result.get()) { - std::cout << "*** SDK successfully initialized!" << std::endl; - } else { - std::cout << "*** SDK failed to initialize" << std::endl; - return 1; - } - } else { - std::cout << "*** SDK initialization didn't complete in " - << INIT_TIMEOUT_MILLISECONDS << "ms" << std::endl; - return 1; - } - - bool const flag_value = client.BoolVariation("my-flag-key", false); - - std::cout << "*** Feature flag 'my-flag-key' is " - << (flag_value ? "true" : "false") << " for this user" << std::endl; - - return 0; -} -``` - -5. Create a `CMakeLists.txt` file and the following content: -```txt -cmake_minimum_required(VERSION 3.19) - -project( - CPPClientQuickstart - VERSION 0.1 - DESCRIPTION "LaunchDarkly CPP Client-side SDK Quickstart" - LANGUAGES CXX -) - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -add_subdirectory(cpp-sdks) - -add_executable(cpp-client-quickstart main.cpp) - -target_link_libraries(cpp-client-quickstart - PRIVATE - launchdarkly::client - Threads::Threads -) -``` - -6. Create a build directory: -```shell -mkdir build && cd build -``` - -7. Configure the SDK with your chosen build system. Examples: -**Make** -```shell -cmake -G"Unix Makefiles" -DBUILD_TESTING=OFF .. -``` -**Ninja** -```shell -cmake -G"Ninja" -DBUILD_TESTING=OFF .. -``` -**Microsoft Visual Studio** -```shell -cmake -G"Visual Studio 17 2022" -DBUILD_TESTING=OFF .. -``` - -8. Build the SDK: -```shell -cmake --build . -``` - -Now that your application is ready, run the application to see what value we get. -```shell -./cpp-client-quickstart -``` - -You should see: -`Feature flag my-flag-key is false` diff --git a/internal/sdks/sdk_instructions/cpp-server-sdk.md b/internal/sdks/sdk_instructions/cpp-server-sdk.md deleted file mode 100644 index cbd0cfca..00000000 --- a/internal/sdks/sdk_instructions/cpp-server-sdk.md +++ /dev/null @@ -1,129 +0,0 @@ -# Installation steps -1. First, ensure the required build dependencies are installed: -- C++ 17 -- CMake 3.19 or higher -- A build system such as make, Ninja, or MSVC -- Boost 1.81 or higher -- OpenSSL - -2. Create a new project directory: -```shell -mkdir hello-cpp-client && cd hello-cpp-client -``` - -3. Clone the C++ SDK inside the directory you created above using git: -```shell -git clone https://github.com/launchdarkly/cpp-sdks.git -``` - -4. Create a file named `main.cpp` and add the following code: -```cpp -#include -#include -#include - -#include -#include - -// Set INIT_TIMEOUT_MILLISECONDS to the amount of time you will wait for -// the client to become initialized. -#define INIT_TIMEOUT_MILLISECONDS 3000 - -using namespace launchdarkly; -using namespace launchdarkly::server_side; - -int main() { - auto config = ConfigBuilder("1234567890abcdef").Build(); - if (!config) { - std::cout << "error: config is invalid: " << config.error() << std::endl; - return 1; - } - - auto client = Client(std::move(*config)); - - auto start_result = client.StartAsync(); - - if (auto const status = start_result.wait_for( - std::chrono::milliseconds(INIT_TIMEOUT_MILLISECONDS)); - status == std::future_status::ready) { - if (start_result.get()) { - std::cout << "*** SDK successfully initialized!" << std::endl; - } else { - std::cout << "*** SDK failed to initialize" << std::endl; - return 1; - } - } else { - std::cout << "*** SDK initialization didn't complete in " - << INIT_TIMEOUT_MILLISECONDS << "ms" << std::endl; - return 1; - } - - auto const context = - ContextBuilder().Kind("user", "example-user-key").Name("Sandy").Build(); - - bool const flag_value = - client.BoolVariation(context, "my-flag-key", false); - - std::cout << "*** Feature flag 'my-flag-key' is " - << (flag_value ? "true" : "false") << std::endl; - - return 0; -} -``` - -5. Create a `CMakeLists.txt` file and the following content: -```txt -cmake_minimum_required(VERSION 3.19) - -project( - CPPServerQuickstart - VERSION 0.1 - DESCRIPTION "LaunchDarkly CPP Server-side SDK Quickstart" - LANGUAGES CXX -) - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -add_subdirectory(cpp-sdks) - -add_executable(cpp-server-quickstart main.cpp) - -target_link_libraries(cpp-server-quickstart - PRIVATE - launchdarkly::server - Threads::Threads -) -``` - -6. Create a build directory: -```shell -mkdir build && cd build -``` - -7. Configure the SDK with your chosen build system: Examples: -**Make** -```shell -cmake -G"Unix Makefiles" -DBUILD_TESTING=OFF .. -``` -**Ninja** -```shell -cmake -G"Unix Makefiles" -DBUILD_TESTING=OFF .. -``` -**Microsoft Visual Studio** -```shell -cmake -G"Visual Studio 17 2022" -DBUILD_TESTING=OFF .. -``` - -8. Build the SDK: -```shell -cmake --build . -``` - -Now that your application is ready, run the application to see what value we get. -```shell -./cpp-server-quickstart -``` - -You should see: -`Feature flag my-flag-key is false` diff --git a/internal/sdks/sdk_instructions/dotnet-client-sdk.md b/internal/sdks/sdk_instructions/dotnet-client-sdk.md deleted file mode 100644 index ef68e9b3..00000000 --- a/internal/sdks/sdk_instructions/dotnet-client-sdk.md +++ /dev/null @@ -1,62 +0,0 @@ -# Installation steps -1. Create a new folder for your project: -```shell -mkdir HelloDotNetClient -cd HelloDotNetClient -``` - -2. Next, create a new console application: -```shell -dotnet new console -``` - -3. Next, add the LaunchDarkly dependency to the project: -```shell -dotnet add package Launchdarkly.ClientSdk -``` - -4. Open the file `Program.cs` and add the following code: -```csharp -using LaunchDarkly.Sdk; -using LaunchDarkly.Sdk.Client; - -var context = Context.New("context-key-123abc"); -var timeSpan = TimeSpan.FromSeconds(10); -var client = LdClient.Init( - Configuration.Default("myMobileKey", ConfigurationBuilder.AutoEnvAttributes.Enabled), - context, - timeSpan -); - -if (client.Initialized) -{ - Console.WriteLine("SDK successfully initialized!"); -} -else -{ - Console.WriteLine("SDK failed to initialize"); - Environment.Exit(1); -} - -var flagValue = client.BoolVariation("my-flag-key", false); - -Console.WriteLine(string.Format("Feature flag 'my-flag-key' is {0}", flagValue)); - -// Here we ensure that the SDK shuts down cleanly and has a chance to deliver analytics -// events to LaunchDarkly before the program exits. If analytics events are not delivered, -// the context properties and flag usage statistics will not appear on your dashboard. In -// a normal long-running application, the SDK would continue running and events would be -// delivered automatically in the background. -client.Dispose(); -``` - -Now that your application is ready, run the application to see what value we get. - -Use the following command to run the code: -```shell -dotnet run -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/dotnet-server-sdk.md b/internal/sdks/sdk_instructions/dotnet-server-sdk.md deleted file mode 100644 index ccf7b494..00000000 --- a/internal/sdks/sdk_instructions/dotnet-server-sdk.md +++ /dev/null @@ -1,114 +0,0 @@ -# Installation steps -1. Open Visual Studio and create a new C# console application. - -2. Next, install the LaunchDarkly SDK using NuGet: -``` -Install-Package LaunchDarkly.ServerSdk -``` - -3. Open the file `Program.cs` and add the following code: -```cs -using System; -using System.Threading.Tasks; -using LaunchDarkly.Sdk; -using LaunchDarkly.Sdk.Server; - -namespace HelloDotNet -{ - class Hello - { - public static void ShowBanner(){ - Console.WriteLine( -@" ██ - ██ - ████████ - ███████ -██ LAUNCHDARKLY █ - ███████ - ████████ - ██ - ██ -"); - } - - static void Main(string[] args) - { - bool CI = Environment.GetEnvironmentVariable("CI") != null; - - string SdkKey = Environment.GetEnvironmentVariable("LAUNCHDARKLY_SDK_KEY"); - - // Set FeatureFlagKey to the feature flag key you want to evaluate. - string FeatureFlagKey = "my-flag-key"; - - if (string.IsNullOrEmpty(SdkKey)) - { - Console.WriteLine("*** Please set LAUNCHDARKLY_SDK_KEY environment variable to your LaunchDarkly SDK key first\n"); - Environment.Exit(1); - } - - var ldConfig = Configuration.Default(SdkKey); - - var client = new LdClient(ldConfig); - - if (client.Initialized) - { - Console.WriteLine("*** SDK successfully initialized!\n"); - } - else - { - Console.WriteLine("*** SDK failed to initialize\n"); - Environment.Exit(1); - } - - // Set up the evaluation context. This context should appear on your LaunchDarkly contexts - // dashboard soon after you run the demo. - var context = Context.Builder("example-user-key") - .Name("Sandy") - .Build(); - - if (Environment.GetEnvironmentVariable("LAUNCHDARKLY_FLAG_KEY") != null) - { - FeatureFlagKey = Environment.GetEnvironmentVariable("LAUNCHDARKLY_FLAG_KEY"); - } - - var flagValue = client.BoolVariation(FeatureFlagKey, context, false); - - Console.WriteLine(string.Format("*** The {0} feature flag evaluates to {1}.\n", - FeatureFlagKey, flagValue)); - - if (flagValue) - { - ShowBanner(); - } - - client.FlagTracker.FlagChanged += client.FlagTracker.FlagValueChangeHandler( - FeatureFlagKey, - context, - (sender, changeArgs) => { - Console.WriteLine(string.Format("*** The {0} feature flag evaluates to {1}.\n", - FeatureFlagKey, changeArgs.NewValue)); - - if (changeArgs.NewValue.AsBool) ShowBanner(); - } - ); - - if(CI) Environment.Exit(0); - - Console.WriteLine("*** Waiting for changes \n"); - - Task waitForever = new Task(() => {}); - waitForever.Wait(); - } - } -} -``` - -Now that your application is ready, run the application to see what value we get. - -If you are using Visual Studio, open HelloDotNet.sln and run the application. Or, to run from the command line, type the following command: -```shell -dotnet run --project HelloDotNet -``` -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/erlang-server-sdk.md b/internal/sdks/sdk_instructions/erlang-server-sdk.md deleted file mode 100644 index c3ac5aae..00000000 --- a/internal/sdks/sdk_instructions/erlang-server-sdk.md +++ /dev/null @@ -1,82 +0,0 @@ -# Installation steps -1. Create a new project for your application: -``` -rebar3 new app hello_erlang && cd hello_erlang -``` - -2. Next, add the SDK package to your list of dependencies in `rebar.config`: -```erlang -{ldclient, "v3.2.0", {pkg, launchdarkly_server_sdk}} -``` - -3. Replace the `ChildSpecs` variable in `src/hello_erlang_sup.erl` with the following: -```erlang -[{console, - {hello_erlang_server, start_link, []}, - permanent, 5000, worker, [hello_erlang_server]}] -``` - -4. Edit `src/hello_erlang.app.src` to import LaunchDarkly: -```erlang -{applications, - [kernel, - stdlib, - ldclient -]}, -``` - -5. First create a new file named `src/hello_erlang_server.erl`. Then, in `src/hello_erlang_server.erl` create a new `LDClient` with your *environment-specific* SDK key: -```erlang --module(hello_erlang_server). --behaviour(gen_server). - --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3]). - --export([start_link/0]). --export([get/3]). - -%% public functions - -start_link() -> - gen_server:start_link({local, hello_erlang_server}, ?MODULE, [], []). - -get(Key, Fallback, ContextKey) -> gen_server:call(?MODULE, {get, Key, Fallback, ContextKey}). - -%% gen_server callbacks - -init(_Args) -> - ldclient:start_instance("1234567890abcdef", #{ - http_options => #{ - tls_options => ldclient_config:tls_basic_options() - } - }), - {ok, []}. - -handle_call({get, Key, Fallback, ContextKey}, _From, State) -> - Flag = ldclient:variation(Key, ldclient_context:new(ContextKey), Fallback), - {reply, Flag, State}. - -handle_cast(_Request, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. -``` - -Now that your application is ready, run the application to see what value we get. -```shell -rebar3 shell -``` -```shell -hello_erlang_server:get(<<"my-flag-key">>, "FALLBACK_VALUE", <<"user@example.com">>). -``` - -You should see: -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/flutter-client-sdk.md b/internal/sdks/sdk_instructions/flutter-client-sdk.md deleted file mode 100644 index 77f4663b..00000000 --- a/internal/sdks/sdk_instructions/flutter-client-sdk.md +++ /dev/null @@ -1,70 +0,0 @@ -# Installation steps -1. Use the Flutter tool to create a new project named `hello_flutter`. -```shell -flutter create hello_flutter --platforms android,ios -``` - -2. Change into the directory of the created project. -```shell -cd hello_flutter -``` - -3. Next, add the LaunchDarkly SDK as a dependency: -```shell -flutter pub add launchdarkly_flutter_client_sdk -``` - -4. Ensure that `ios/Podfile` specifies a minimum deployment target of at least 10.0. -```shell -platform :ios, '10.0' -``` - -5. Ensure that `android/app/build.gradle` specifies a `minSdkVersion` of at least 21. -```shell -minSdkVersion 21 -``` - -6. Open the file `lib/main.dart` and add the following code: -```dart -// Import the LaunchDarkly SDK. -import 'package:launchdarkly_flutter_client_sdk/launchdarkly_flutter_client_sdk.dart'; - -void main() async { - // If initializing the SDK within a widget, this line will not be needed. - WidgetsFlutterBinding.ensureInitialized(); - - // Configure the SDK with your mobile-specific SDK key and context. - // If building a web application the client-side ID should be used instead. - // For a more complete example refer to: - // https://github.com/launchdarkly/flutter-client-sdk/tree/main/packages/flutter_client_sdk/example - final ldClient = LDClient(LDConfig('myMobileKey', AutoEnvAttributes.enabled), - LDContextBuilder().kind('user', 'example-user-key').build()); - - try { - await ldClient.start().timeout(const Duration(seconds: 5)); - // Wait for up-to-date flags for the context, or cached flags if the - // SDK has seen this context before. - } catch(exception) { - // Initialization timed out. The SDK can still be used even if - // this times out. - } - - // Call LaunchDarkly with the feature flag key you want to evaluate. - final result = ldClient.boolVariation('my-flag-key', false); - // Send analytic events to LaunchDarkly. By default events are flushed every - // 30 seconds, and you don't need to call flush manually. - await ldClient.flush(); - - runApp(const MyApp()); -} -``` -Now that your application is ready, run the application to see what value we get. - -Run the Android or iOS simulator and then run the application. -```shell -flutter run -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/go-server-sdk.md b/internal/sdks/sdk_instructions/go-server-sdk.md deleted file mode 100644 index 1a1fc3fe..00000000 --- a/internal/sdks/sdk_instructions/go-server-sdk.md +++ /dev/null @@ -1,107 +0,0 @@ -# Installation steps -1. Create a new directory for your application: -```shell -mkdir hello-go && cd hello-go -``` - -2. Start your module using the go mod init command: -```shell -go mod init example/hello-go -``` - -3. Next, install the SDK (SDK v7 requires go 1.18+): -```shell -go get github.com/launchdarkly/go-server-sdk/v7 -``` - -4. Create a file called main.go and add the following code: -```go -package main - -import ( - "fmt" - "os" - "time" - - "github.com/launchdarkly/go-sdk-common/v3/ldcontext" - "github.com/launchdarkly/go-sdk-common/v3/ldvalue" - ld "github.com/launchdarkly/go-server-sdk/v7" -) - -func showBanner() { - fmt.Print("\n ██ \n" + - " ██ \n" + - " ████████ \n" + - " ███████ \n" + - "██ LAUNCHDARKLY █\n" + - " ███████ \n" + - " ████████ \n" + - " ██ \n" + - " ██ \n") -} - -func showMessage(s string) { fmt.Printf("*** %%s\n\n", s) } - -func main() { - var sdkKey = os.Getenv("LAUNCHDARKLY_SDK_KEY") - - if sdkKey == "" { - showMessage("LaunchDarkly SDK key is required: set the LAUNCHDARKLY_SDK_KEY environment variable and try again.") - os.Exit(1) - } - - ldClient, _ := ld.MakeClient(sdkKey, 5*time.Second) - if ldClient.Initialized() { - showMessage("SDK successfully initialized!") - } else { - showMessage("SDK failed to initialize") - os.Exit(1) - } - - // Set up the evaluation context. This context should appear on your LaunchDarkly contexts dashboard - // soon after you run the demo. - context := ldcontext.NewBuilder("example-user-key"). - Name("Sandy"). - Build() - - // Set featureFlagKey to the feature flag key you want to evaluate. - var featureFlagKey = "my-flag-key" - - if os.Getenv("LAUNCHDARKLY_FLAG_KEY") != "" { - featureFlagKey = os.Getenv("LAUNCHDARKLY_FLAG_KEY") - } - - flagValue, err := ldClient.BoolVariation(featureFlagKey, context, false) - if err != nil { - showMessage("error: " + err.Error()) - } - - showMessage(fmt.Sprintf("The '%%s' feature flag evaluates to %%t.", featureFlagKey, flagValue)) - - if flagValue { - showBanner() - } - - if os.Getenv("CI") != "" { - os.Exit(0) - } - - updateCh := ldClient.GetFlagTracker().AddFlagValueChangeListener(featureFlagKey, context, ldvalue.Null()) - - for event := range updateCh { - showMessage(fmt.Sprintf("The '%%s' feature flag evaluates to %%t.", featureFlagKey, event.NewValue.BoolValue())) - if event.NewValue.BoolValue() { - showBanner() - } - } -} -``` - -Now that your application is ready, run the application to see what value we get. -```shell -go build && LAUNCHDARKLY_SDK_KEY='1234567890abcdef' ./hello-go -``` - -You should see: - -`*** The 'my-flag-key' feature flag evaluates to false.` diff --git a/internal/sdks/sdk_instructions/haskell-server-sdk.md b/internal/sdks/sdk_instructions/haskell-server-sdk.md deleted file mode 100644 index 94b3982a..00000000 --- a/internal/sdks/sdk_instructions/haskell-server-sdk.md +++ /dev/null @@ -1,109 +0,0 @@ -# Installation steps -1. Create a new project for your application: -```shell -stack new hello-haskell && cd hello-haskell -``` - -2. Next, add the SDK and text package to your list of dependencies in package.yaml: -```shell -launchdarkly-server-sdk, text -``` - -3. Add the SDK version as an `extra-deps` entry in `stack.yaml`: -```shell -- launchdarkly-server-sdk-4.1.0 -``` - -4. Edit `app/Main.hs` by adding the following code: -```haskell -{-# LANGUAGE OverloadedStrings, NumericUnderscores #-} -module Main where -import Control.Concurrent (threadDelay) -import Control.Monad (forever) -import Data.Text (Text, pack) -import Data.Function ((&)) -import qualified LaunchDarkly.Server as LD -import System.Timeout (timeout) -import Text.Printf (printf, hPrintf) -import System.Environment (lookupEnv) - -showEvaluationResult :: String -> Bool -> IO () -showEvaluationResult key value = do - printf "*** The %%s feature flag evaluates to %%s\n" key (show value) - -showBanner :: IO () -showBanner = putStr "\n\ -\ ██ \n\ -\ ██ \n\ -\ ████████ \n\ -\ ███████ \n\ -\██ LAUNCHDARKLY █\n\ -\ ███████ \n\ -\ ████████ \n\ -\ ██ \n\ -\ ██ \n\ -\\n\ -\" - -showMessage :: String -> Bool -> Maybe Bool -> Bool -> IO Bool -showMessage key True _ True = do - showBanner - showEvaluationResult key True - pure False -showMessage key value Nothing showBanner = do - showEvaluationResult key value - pure showBanner -showMessage key value (Just lastValue) showBanner - | value /= lastValue = do - showEvaluationResult key value - pure showBanner - | otherwise = pure showBanner - -waitForClient :: LD.Client -> IO Bool -waitForClient client = do - status <- LD.getStatus client - case status of - LD.Uninitialized -> threadDelay (1 * 1_000) >> waitForClient client - LD.Initialized -> return True - _anyOtherStatus -> return False - -evaluateLoop :: LD.Client -> String -> LD.Context -> Maybe Bool -> Bool -> IO () -evaluateLoop client featureFlagKey context lastValue showBanner = do - value <- LD.boolVariation client (pack featureFlagKey) context False - showBanner' <- showMessage featureFlagKey value lastValue showBanner - - threadDelay (1 * 1_000_000) >> evaluateLoop client featureFlagKey context (Just value) showBanner' - -evaluate :: Maybe String -> Maybe String -> IO () -evaluate (Just sdkKey) Nothing = do evaluate (Just sdkKey) (Just "sample-feature") -evaluate (Just sdkKey) (Just featureFlagKey) = do - -- Set up the evaluation context. This context should appear on your - -- LaunchDarkly contexts dashboard soon after you run the demo. - let context = LD.makeContext "example-user-key" "user" & LD.withName "Sandy" - client <- LD.makeClient $ LD.makeConfig (pack sdkKey) - initialized <- timeout (5_000 * 1_000) (waitForClient client) - - case initialized of - Just True -> do - print "*** SDK successfully initialized!" - evaluateLoop client featureFlagKey context Nothing True - _notInitialized -> putStrLn "*** SDK failed to initialize. Please check your internet connection and SDK credential for any typo." -evaluate _ _ = putStrLn "*** You must define LAUNCHDARKLY_SDK_KEY and LAUNCHDARKLY_FLAG_KEY before running this script" - -main :: IO () -main = do - -- Set sdkKey to your LaunchDarkly SDK key. - sdkKey <- lookupEnv "LAUNCHDARKLY_SDK_KEY" - -- Set featureFlagKey to the feature flag key you want to evaluate. - featureFlagKey <- lookupEnv "my-flag-key" - evaluate sdkKey featureFlagKey -``` - -Now that your application is ready, run the application to see what value we get. -```shell -stack build && stack exec hello-haskell-exe -``` - -You should see: - -`Feature flag my-flag-key is FALSE` diff --git a/internal/sdks/sdk_instructions/java-server-sdk.md b/internal/sdks/sdk_instructions/java-server-sdk.md deleted file mode 100644 index 606cf61e..00000000 --- a/internal/sdks/sdk_instructions/java-server-sdk.md +++ /dev/null @@ -1,170 +0,0 @@ -# Installation steps -1. Create a new project and accept the default options suggested by maven: -```shell -mvn archetype:generate -DgroupId=com.launchdarkly.tutorial -DartifactId=hello-java -``` - -2. Change into the project directory: -```shell -cd hello-java -``` - -3. Add the SDK to your project in your `pom.xml ` section: -```xml - - com.launchdarkly - launchdarkly-java-server-sdk - 7.4.0 - -``` - -4. Configure the Maven Assembly Plugin in your `pom.xml` to make it easier to run the application: -```xml - - - - maven-assembly-plugin - - - - com.launchdarkly.tutorial.App - - - - jar-with-dependencies - - - - - -``` - -5. Depending on your java version, you may need to change the compilation source and target level in `pom.xml`: -```xml -1.8 - 1.8 -``` - -6. Add the following code to `App.java`: -```java -import java.io.IOException; - -import com.launchdarkly.sdk.*; -import com.launchdarkly.sdk.server.*; - -public class App { - - // Set SDK_KEY to your LaunchDarkly SDK key. - static String SDK_KEY = "YOUR_SDK_KEY"; - - // Set FEATURE_FLAG_KEY to the feature flag key you want to evaluate. - static String FEATURE_FLAG_KEY = "my-flag-key"; - - private static void showMessage(String s) { - System.out.println("*** " + s); - System.out.println(); - } - - private static void showBanner() { - showMessage("\n ██ \n" + - " ██ \n" + - " ████████ \n" + - " ███████ \n" + - "██ LAUNCHDARKLY █\n" + - " ███████ \n" + - " ████████ \n" + - " ██ \n" + - " ██ \n"); - } - - public static void main(String... args) throws Exception { - boolean CIMode = System.getenv("CI") != null; - - String envSDKKey = System.getenv("LAUNCHDARKLY_SDK_KEY"); - if(envSDKKey != null) { - SDK_KEY = envSDKKey; - } - - String envFlagKey = System.getenv("LAUNCHDARKLY_FLAG_KEY"); - if(envFlagKey != null) { - FEATURE_FLAG_KEY = envFlagKey; - } - - LDConfig config = new LDConfig.Builder().build(); - - if (SDK_KEY == null || SDK_KEY.equals("")) { - showMessage("Please set the LAUNCHDARKLY_SDK_KEY environment variable or edit Hello.java to set SDK_KEY to your LaunchDarkly SDK key first."); - System.exit(1); - } - - final LDClient client = new LDClient(SDK_KEY, config); - if (client.isInitialized()) { - showMessage("SDK successfully initialized!"); - } else { - showMessage("SDK failed to initialize. Please check your internet connection and SDK credential for any typo."); - System.exit(1); - } - - // Set up the evaluation context. This context should appear on your - // LaunchDarkly contexts dashboard soon after you run the demo. - final LDContext context = LDContext.builder("example-user-key") - .name("Sandy") - .build(); - - // Evaluate the feature flag for this context. - boolean flagValue = client.boolVariation(FEATURE_FLAG_KEY, context, false); - showMessage("The '" + FEATURE_FLAG_KEY + "' feature flag evaluates to " + flagValue + "."); - - if (flagValue) { - showBanner(); - } - - //If this is building for CI, we don't need to keep running the Hello App continously. - if(CIMode) { - System.exit(0); - } - - // We set up a flag change listener so you can see flag changes as you change - // the flag rules. - client.getFlagTracker().addFlagValueChangeListener(FEATURE_FLAG_KEY, context, event -> { - showMessage("The '" + FEATURE_FLAG_KEY + "' feature flag evaluates to " + event.getNewValue() + "."); - - if (event.getNewValue().booleanValue()) { - showBanner(); - } - }); - showMessage("Listening for feature flag changes."); - - // Here we ensure that when the application terminates, the SDK shuts down - // cleanly and has a chance to deliver analytics events to LaunchDarkly. - // If analytics events are not delivered, the context attributes and flag usage - // statistics may not appear on your dashboard. In a normal long-running - // application, the SDK would continue running and events would be delivered - // automatically in the background. - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - public void run() { - try { - client.close(); - } catch (IOException e) { - // ignore - } - } - }, "ldclient-cleanup-thread")); - - // Keeps example application alive. - Object mon = new Object(); - synchronized (mon) { - mon.wait(); - } - } -} -``` - -Now that your application is ready, run the application to see what value we get. -```shell -mvn clean compile assembly:single && LAUNCHDARKLY_SDK_KEY=YOUR_SDK_KEY java -jar target/hello-java-1.0-SNAPSHOT-jar-with-dependencies.jar -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/js-client-sdk.md b/internal/sdks/sdk_instructions/js-client-sdk.md deleted file mode 100644 index 0c0d1ea5..00000000 --- a/internal/sdks/sdk_instructions/js-client-sdk.md +++ /dev/null @@ -1,70 +0,0 @@ -# Installation steps -1. Create a file called `index.html` and add the following code: -```html - - - - - - LaunchDarkly tutorial - - - - - - -``` -Now that your application is ready, run the application to see what value we get. - -Open `index.html` in your browser. You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/lua-server-sdk.md b/internal/sdks/sdk_instructions/lua-server-sdk.md deleted file mode 100644 index bfad1b0b..00000000 --- a/internal/sdks/sdk_instructions/lua-server-sdk.md +++ /dev/null @@ -1,56 +0,0 @@ -# Installation steps -1. Lua is a wrapper SDK that depends on the C++ Server SDK. The following dependencies assume you will build the C++ Server SDK from source. If you already have the C+ Server SDK installed, only LuaRocks is required. - -First, ensure the required build dependencies are installed: -- [LuaRocks](https://luarocks.org/) -- C++ 17 -- [CMake 3.19 or higher](https://cmake.org/) -- A build system such as [make](https://www.gnu.org/software/make/manual/make.html), [Ninja](https://ninja-build.org/), or [MSVC](https://visualstudio.microsoft.com/) -- [Boost 1.81](https://www.boost.org/) or higher -- [OpenSSL](https://www.openssl.org/) - -2. If the C++ Server SDK is already installed or you already obtained [release artifacts](https://github.com/launchdarkly/cpp-sdks/releases?q="launchdarkly-cpp-server") from LaunchDarkly, skip to step 3. - -Otherwise, compile and install the C++ Server SDK: -```shell -git clone https://github.com/launchdarkly/cpp-sdks.git && cd cpp-sdks -mkdir build && cd build -cmake -G Ninja -D BUILD_TESTING=OFF \ - -D CMAKE_BUILD_TYPE=Release \ - -D LD_BUILD_SHARED_LIBS=On \ - -D CMAKE_INSTALL_PREFIX=./install .. -cmake --build . --target launchdarkly-cpp-server -cmake --install . -cd ../../ -``` - -3. Download the Lua Server SDK and build it with `luarocks` (replace `LD_DIR` with the path to the C++ SDK's shared libraries as necessary): -```shell -luarocks install launchdarkly-server-sdk LD_DIR="$(pwd)/cpp-sdks/build/install" -``` - -4. Create a file named hello.lua and add the following code: -```lua -local ld = require("launchdarkly_server_sdk") -local config = {} - -local client = ld.clientInit("1234567890abcdef", 1000, config) - -local user = ld.makeContext({ - user = { - key = "example-user-key", - name = "Sandy" - } -}) - -local value = client:boolVariation(user, "my-flag-key", false) -print("Feature flag 'my-flag-key' is "..tostring(value).."")` -``` - -Now that your application is ready, run the application to see what value we get. -```lua -lua hello.lua -``` - -You should see: -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/node-client-sdk.md b/internal/sdks/sdk_instructions/node-client-sdk.md deleted file mode 100644 index 58523f32..00000000 --- a/internal/sdks/sdk_instructions/node-client-sdk.md +++ /dev/null @@ -1,56 +0,0 @@ -# Installation steps -1. Create a new directory and create a `package.json` file: -```shell -mkdir hello-node-client && cd hello-node-client && npm init -``` - -2. Next, install the LaunchDarkly SDK: -```shell -npm install launchdarkly-node-client-sdk@3.1.0 --save -``` - -3. Create a file called index.js and add the following code: -```js -// Import the LaunchDarkly client -var LaunchDarkly = require('launchdarkly-node-client-sdk'); - -// Set up the user properties. This user should appear on your LaunchDarkly users dashboard -// soon after you run the demo. -var user = { - key: "example-user-key" -}; - -// Create a single instance of the LaunchDarkly client -const ldClient = LaunchDarkly.initialize('1234567890abcdef', user); - -function showMessage(s) { - console.log("*** " + s); - console.log(""); -} -ldClient.waitForInitialization().then(function() { - showMessage("SDK successfully initialized!"); - const flagValue = ldClient.variation("my-flag-key", false); - - showMessage("Feature flag " + "my-flag-key" + " is " + flagValue + "); - - // Here we ensure that the SDK shuts down cleanly and has a chance to deliver analytics - // events to LaunchDarkly before the program exits. If analytics events are not delivered, - // the user properties and flag usage statistics will not appear on your dashboard. In a - // normal long-running application, the SDK would continue running and events would be - // delivered automatically in the background. - ldClient.close(); -}).catch(function(error) { - showMessage("SDK failed to initialize: " + error); - process.exit(1); -}); -``` - -In your real application, you should only call `close()` when your application is terminating -- not immediately following each `variation` call as shown in this tutorial. This is something you only need to do once. - -Now that your application is ready, run the application to see what value we get. -```shell -node index.js -``` - -You should see: -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/node-server.md b/internal/sdks/sdk_instructions/node-server.md deleted file mode 100644 index 38f50559..00000000 --- a/internal/sdks/sdk_instructions/node-server.md +++ /dev/null @@ -1,89 +0,0 @@ -# Installation steps -1. Create a new directory and create a `package.json` file: -```shell -mkdir hello-node-server && cd hello-node-server && npm init -``` - -2. Next, install the LaunchDarkly SDK: -```shell -npm install @launchdarkly/node-server-sdk@9.4.1 --save -``` - -3. Create a file called `index.js` and add the following code: -```js -const LaunchDarkly = require('@launchdarkly/node-server-sdk'); - -// Set sdkKey to your LaunchDarkly SDK key. -const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY ?? 'YOUR_SDK_KEY'; - -// Set featureFlagKey to the feature flag key you want to evaluate. -const featureFlagKey = 'my-flag-key'; - -function showBanner() { - console.log( - ` ██ - ██ - ████████ - ███████ -██ LAUNCHDARKLY █ - ███████ - ████████ - ██ - ██ -`, - ); -} - -function printValueAndBanner(flagValue) { - console.log(`*** The '${featureFlagKey}' feature flag evaluates to ${flagValue}.`); - - if (flagValue) showBanner(); -} - -if (!sdkKey) { - console.log('*** Please edit index.js to set sdkKey to your LaunchDarkly SDK key first.'); - process.exit(1); -} - -const ldClient = LaunchDarkly.init(sdkKey); - -// Set up the context properties. This context should appear on your LaunchDarkly contexts dashboard -// soon after you run the demo. -const context = { - kind: 'user', - key: 'example-user-key', - name: 'Sandy', -}; - -ldClient - .waitForInitialization() - .then(() => { - console.log('*** SDK successfully initialized!'); - - const eventKey = `update:${featureFlagKey}`; - ldClient.on(eventKey, () => { - ldClient.variation(featureFlagKey, context, false).then(printValueAndBanner); - }); - - ldClient.variation(featureFlagKey, context, false).then((flagValue) => { - printValueAndBanner(flagValue); - - if(typeof process.env.CI !== "undefined") { - process.exit(0); - } - }); - }) - .catch((error) => { - console.log(`*** SDK failed to initialize: ${error}`); - process.exit(1); - }); -``` - -Now that your application is ready, run the application to see what value we get. -```shell -LAUNCHDARKLY_SDK_KEY=YOUR_SDK_KEY node index.js -``` - -You should see: - -`Feature flag my-new-flag is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/php-server-sdk.md b/internal/sdks/sdk_instructions/php-server-sdk.md deleted file mode 100644 index 88257365..00000000 --- a/internal/sdks/sdk_instructions/php-server-sdk.md +++ /dev/null @@ -1,88 +0,0 @@ -# Installation steps -1. Create a new directory and install [Composer](https://getcomposer.org/): -```shell -mkdir hello-php && cd hello-php && curl -sS https://getcomposer.org/installer | php -``` - -2. Next, install the LaunchDarkly SDK and Guzzle dependency: -```shell -php composer.phar require launchdarkly/server-sdk:6.1.0 guzzlehttp/guzzle -``` - -3. Create a file called `index.php` and add the following code: -```php -kind("user") -->name("Sandy") -->build(); - - -$showBanner = true; -$lastValue = null; -do { - $flagValue = $client->variation($featureFlagKey, $context, false); - - if ($flagValue !== $lastValue) { - showEvaluationResult($featureFlagKey, $flagValue); - } - - if ($showBanner && $flagValue) { - showBanner(); - $showBanner = false; - } - - $lastValue = $flagValue; - sleep(1); -} while(true); -``` - -Now that your application is ready, run the application to see what value we get. -```shell -LAUNCHDARKLY_SDK_KEY=YOUR_SDK_KEY php index.php -``` - -You should see: - -`Feature flag my-flag-key is FALSE` diff --git a/internal/sdks/sdk_instructions/python-server-sdk.md b/internal/sdks/sdk_instructions/python-server-sdk.md deleted file mode 100644 index 8d8f09f6..00000000 --- a/internal/sdks/sdk_instructions/python-server-sdk.md +++ /dev/null @@ -1,106 +0,0 @@ -# Installation steps -1. Create a new directory: - -```bash -mkdir hello-python && cd hello-python -``` - -2. Next, create a file called `requirements.txt` with the SDK dependency and install it: - -```bash -echo "launchdarkly-server-sdk==9.4.0" >> requirements.txt && pip install -r requirements.txt -``` - -3. Create a file called `test.py` and add the following code: - -```python -import os -import ldclient -from ldclient import Context -from ldclient.config import Config -from threading import Lock, Event - - -# Set sdk_key to your LaunchDarkly SDK key. -sdk_key = os.getenv("LAUNCHDARKLY_SDK_KEY") - -# Set feature_flag_key to the feature flag key you want to evaluate. -feature_flag_key = "my-flag-key" - - -def show_evaluation_result(key: str, value: bool): - print() - print(f"*** The {key} feature flag evaluates to {value}") - - -def show_banner(): - print() - print(" ██ ") - print(" ██ ") - print(" ████████ ") - print(" ███████ ") - print("██ LAUNCHDARKLY █") - print(" ███████ ") - print(" ████████ ") - print(" ██ ") - print(" ██ ") - print() - - -class FlagValueChangeListener: - def __init__(self): - self.__show_banner = True - self.__lock = Lock() - - def flag_value_change_listener(self, flag_change): - with self.__lock: - if self.__show_banner and flag_change.new_value: - show_banner() - self.__show_banner = False - - show_evaluation_result(flag_change.key, flag_change.new_value) - - -if __name__ == "__main__": - if not sdk_key: - print("*** Please set the LAUNCHDARKLY_SDK_KEY env first") - exit() - if not feature_flag_key: - print("*** Please set the LAUNCHDARKLY_FLAG_KEY env first") - exit() - - ldclient.set_config(Config(sdk_key)) - - if not ldclient.get().is_initialized(): - print("*** SDK failed to initialize. Please check your internet connection and SDK credential for any typo.") - exit() - - print("*** SDK successfully initialized") - - # Set up the evaluation context. This context should appear on your - # LaunchDarkly contexts dashboard soon after you run the demo. - context = \ - Context.builder('example-user-key').kind('user').name('Sandy').build() - - flag_value = ldclient.get().variation(feature_flag_key, context, False) - show_evaluation_result(feature_flag_key, flag_value) - - change_listener = FlagValueChangeListener() - listener = ldclient.get().flag_tracker \ - .add_flag_value_change_listener(feature_flag_key, context, change_listener.flag_value_change_listener) - - try: - Event().wait() - except KeyboardInterrupt: - pass -``` - -Now that your application is ready, run the application to see what value we get. - -```shell -LAUNCHDARKLY_SDK_KEY=YOUR_SDK_KEY python test.py -``` - -You should see: - -`Feature flag my-flag-key is false` diff --git a/internal/sdks/sdk_instructions/react-client-sdk.md b/internal/sdks/sdk_instructions/react-client-sdk.md deleted file mode 100644 index 82cb69a9..00000000 --- a/internal/sdks/sdk_instructions/react-client-sdk.md +++ /dev/null @@ -1,75 +0,0 @@ -# Installation steps -1. Use create-react-app to create a new React application: - -```shell -npx create-react-app hello-react --template typescript && cd hello-react -``` - -2. Install the LaunchDarkly SDK: - -```shell -npm install --save launchdarkly-react-client-sdk@3.1.0 -``` - -3. In `index.tsx`: - -```tsx -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk'; - -(async () => { - const LDProvider = await asyncWithLDProvider({ - clientSideID: 'myClientSideId', - context: { - kind: 'user', - key: 'example-user', - name: 'Example user', - }, - }); - - const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); - root.render( - - - - - , - ); -})(); -``` - -4. In `App.tsx`: - -```tsx -import './App.css'; -import { useFlags } from 'launchdarkly-react-client-sdk'; - -function App() { - const { myFlagKey } = useFlags(); - - return ( -
-
-

{myFlagKey ? Flag on : Flag off}

-
-
- ); -} - -export default App; -``` - -Note that `my-flag-key` is accessed in camel-cased form. Read [our documentation](https://docs.launchdarkly.com/sdk/client-side/react/react-web?site=launchDarkly#flag-keys) to learn more about referencing flag keys in React. - -Now that your application is ready, run the application to see what value we get. - -```shell -npm start -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/react-native.md b/internal/sdks/sdk_instructions/react-native.md deleted file mode 100644 index af944a50..00000000 --- a/internal/sdks/sdk_instructions/react-native.md +++ /dev/null @@ -1,77 +0,0 @@ -# Installation steps -1. Use create-expo-app to create a new Expo application: -```shell -npx create-expo-app hello-react-native -t expo-template-blank-typescript && cd hello-react-native -``` - -2. Install the LaunchDarkly SDK: -```shell -yarn add @launchdarkly/react-native-client-sdk -``` - -3. In `App.tsx`: -```tsx -import { - AutoEnvAttributes, - LDProvider, - ReactNativeLDClient, -} from '@launchdarkly/react-native-client-sdk'; - -import Welcome from './src/welcome'; - -const featureClient = new ReactNativeLDClient( - 'myMobileKey', - AutoEnvAttributes.Enabled, - { - debug: true, - applicationInfo: { - id: 'ld-rn-test-app', - version: '0.0.1', - }, - }, -); - -const App = () => { - return ( - - - - ); -}; - -export default App; -``` - -4. Create a new file `src/welcome.tsx`: -```tsx -import { useEffect } from 'react'; -import { Text, View } from 'react-native'; - -import { useBoolVariation, useLDClient } from '@launchdarkly/react-native-client-sdk'; - -export default function Welcome() { - const flagValue = useBoolVariation('my-flag-key', false); - const ldc = useLDClient(); - - useEffect(() => { - ldc - .identify({ kind: 'user', key: 'example-user' }) - .catch((e: any) => console.error('error: ' + e)); - }, []); - - return ( - - my-flag-key: {flagValue.toString()} - - ); -} -``` - -Now that your application is ready, run the application to see what value we get. -```shell -yarn ios -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdk_instructions/roku.md b/internal/sdks/sdk_instructions/roku.md deleted file mode 100644 index 4bada58c..00000000 --- a/internal/sdks/sdk_instructions/roku.md +++ /dev/null @@ -1,137 +0,0 @@ -# Installation steps -1. Create a new directory for your application: -```shell -mkdir hello-roku && cd hello-roku && mkdir components source -``` - -2. Download the latest version of the SDK [from the release page](https://github.com/launchdarkly/roku-client-sdk/releases). `PlaceLaunchDarkly.brs` in `source`, and the other two files in `components`. - - -3. Create a basic app `manifest` file. -```shell -title=hello-roku -major_version=1 -minor_version=0 -build_version=00001 -``` - -4. Create a file with a basic scene runner named `source/main.brs` and add the following code: -```brs -sub main(params as object) - screen = createObject("roSGScreen") - messagePort = createObject("roMessagePort") - screen.setMessagePort(messagePort) - - scene = screen.CreateScene("AppScene") - - screen.show() - - while (true) - msg = wait(2500, messagePort) - - if type(msg) = "roSGScreenEvent" - if msg.isScreenClosed() then - return - end if - end if - end while -end sub -``` - -5. In `components/AppScene.xml` create a basic scene by adding the following code: -```xml - - - - - - - - - - -``` - -5. Now that your application is ready, run the application to see what value we get. -```shell -yarn dev -``` - -You should see: - -`Feature flag my-flag-key is FALSE for this context` diff --git a/internal/sdks/sdks.go b/internal/sdks/sdks.go deleted file mode 100644 index 289432c0..00000000 --- a/internal/sdks/sdks.go +++ /dev/null @@ -1,56 +0,0 @@ -package sdks - -import ( - "embed" - "regexp" - "strings" -) - -//go:embed sdk_instructions/*.md -var InstructionFiles embed.FS - -// ReplaceFlagKey changes the placeholder flag key in the SDK instructions to the flag key from -// the user. -func ReplaceFlagKey(instructions string, key string) string { - r := strings.NewReplacer( - "my-flag-key", - key, - "myFlagKey", - kebabToCamel(key), - ) - - return r.Replace(instructions) -} - -// ReplaceSDKKeys changes the placeholder SDK key/client side ID in the SDK instructions to the key from -// the default test environment for the user's account. -func ReplaceSDKKeys(instructions string, sdkKey, clientSideId, mobileKey string) string { - r := strings.NewReplacer( - "1234567890abcdef", - sdkKey, - "myClientSideId", - clientSideId, - "myMobileKey", - mobileKey, - // remove remaining values when we add all hardcoded instructions - "mobile-key-from-launch-darkly-website", - sdkKey, - "YOUR_SDK_KEY", - sdkKey, - "Your LaunchDarkly SDK key", - sdkKey, - "myClientSideID", - clientSideId, - ) - - return r.Replace(instructions) -} - -// kebabToCamel converts a kebab-case key string into a camelCase key string, used for the React sdk instructions -func kebabToCamel(kebabCase string) string { - replaceDashRegex := regexp.MustCompile(`-(.)`) - camelCase := replaceDashRegex.ReplaceAllStringFunc(kebabCase, func(match string) string { - return strings.ToUpper(string(match[1])) - }) - return camelCase -} diff --git a/internal/sdks/sdks_test.go b/internal/sdks/sdks_test.go deleted file mode 100644 index 08de2119..00000000 --- a/internal/sdks/sdks_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package sdks_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/launchdarkly/ldcli/internal/sdks" -) - -func TestReplaceFlagKey(t *testing.T) { - tests := map[string]struct { - body string - expected string - }{ - "replaces placeholder my-flag-key": { - body: "# title ```const featureFlagKey = \"my-flag-key\"```", - expected: "# title ```const featureFlagKey = \"real-flag-key\"```", - }, - "replaces camelCase ": { - body: "# title ```const featureFlagKey = \"myFlagKey\"```", - expected: "# title ```const featureFlagKey = \"realFlagKey\"```", - }, - "does not replace BOOLEAN_FLAG_KEY": { - body: "# title ```val BOOLEAN_FLAG_KEY = \"myFlagKey\"```", - expected: "# title ```val BOOLEAN_FLAG_KEY = \"realFlagKey\"```", - }, - } - for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { - updated := sdks.ReplaceFlagKey(tt.body, "real-flag-key") - - assert.Equal(t, string(tt.expected), string(updated)) - }) - } -} - -func TestReplaceSDKKey(t *testing.T) { - tests := map[string]struct { - body string - expected string - }{ - "replaces placeholder 1234567890abcdef": { - body: "# title ```const sdkKey = \"1234567890abcdef\"```", - expected: "# title ```const sdkKey = \"real-sdk-key\"```", - }, - "replaces placeholder myClientSideId": { - body: "# title ```const sdkKey = \"myClientSideId\"```", - expected: "# title ```const sdkKey = \"real-client-side-id\"```", - }, - "replaces placeholder mobile-key-from-launch-darkly-website": { - body: "# title ```const sdkKey = \"mobile-key-from-launch-darkly-website\"```", - expected: "# title ```const sdkKey = \"real-sdk-key\"```", - }, - "replaces placeholder YOUR_SDK_KEY": { - body: "# title ```const sdkKey = \"YOUR_SDK_KEY\"```", - expected: "# title ```const sdkKey = \"real-sdk-key\"```", - }, - "replaces placeholder Your LaunchDarkly SDK key": { - body: "# title ```const sdkKey = \"Your LaunchDarkly SDK key\"```", - expected: "# title ```const sdkKey = \"real-sdk-key\"```", - }, - "replaces placeholder myMobileKey": { - body: "# title ```const sdkKey = \"myMobileKey\"```", - expected: "# title ```const sdkKey = \"real-mobile-key\"```", - }, - } - for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { - updated := sdks.ReplaceSDKKeys(tt.body, "real-sdk-key", "real-client-side-id", "real-mobile-key") - - assert.Equal(t, string(tt.expected), string(updated)) - }) - } -} From fa6d68309e0f95ecbc85ff9df55b3dcece859885 Mon Sep 17 00:00:00 2001 From: Ramon Niebla Date: Fri, 1 May 2026 10:05:14 -0700 Subject: [PATCH 2/2] refactor(analytics): drop wizard-only tracker methods The setup wizard was the sole consumer of SendSetupStepStartedEvent, SendSetupSDKSelectedEvent, and SendSetupFlagToggledEvent. With the wizard gone, shrink the Tracker interface and remove the methods from all four implementations and the corresponding test. Co-authored-by: Cursor --- internal/analytics/client.go | 29 ----------------------------- internal/analytics/client_test.go | 20 -------------------- internal/analytics/log_client.go | 9 --------- internal/analytics/mock.go | 29 ----------------------------- internal/analytics/noop_client.go | 9 +++------ internal/analytics/tracker.go | 3 --- 6 files changed, 3 insertions(+), 96 deletions(-) diff --git a/internal/analytics/client.go b/internal/analytics/client.go index 2a8ca524..4b1b06ab 100644 --- a/internal/analytics/client.go +++ b/internal/analytics/client.go @@ -116,35 +116,6 @@ func (c *Client) SendCommandCompletedEvent(outcome string) { ) } -func (c *Client) SendSetupStepStartedEvent(step string) { - c.sendEvent( - "CLI Setup Step Started", - map[string]interface{}{ - "step": step, - }, - ) -} - -func (c *Client) SendSetupSDKSelectedEvent(sdk string) { - c.sendEvent( - "CLI Setup SDK Selected", - map[string]interface{}{ - "sdk": sdk, - }, - ) -} - -func (c *Client) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { - c.sendEvent( - "CLI Setup Flag Toggled", - map[string]interface{}{ - "on": on, - "count": count, - "duration_ms": duration_ms, - }, - ) -} - func (a *Client) Wait() { a.wg.Wait() } diff --git a/internal/analytics/client_test.go b/internal/analytics/client_test.go index 5bb182f5..e286aa86 100644 --- a/internal/analytics/client_test.go +++ b/internal/analytics/client_test.go @@ -157,24 +157,4 @@ func TestClient_SendEvent(t *testing.T) { assert.Equal(t, "/internal/tracking", requestPath) }) - - t.Run("SendSetupStepStartedEvent sends correct event name", func(t *testing.T) { - var received trackingPayload - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, _ := io.ReadAll(r.Body) - _ = json.Unmarshal(body, &received) - w.WriteHeader(http.StatusOK) - })) - defer server.Close() - - fn := ClientFn{ID: "test-id", Version: "1.0.0", AgentContext: "codex"} - tracker := fn.Tracker("test-token", server.URL, false) - - tracker.SendSetupStepStartedEvent("connect") - tracker.Wait() - - assert.Equal(t, "CLI Setup Step Started", received.Event) - assert.Equal(t, "connect", received.Properties["step"]) - assert.Equal(t, "codex", received.Properties["agent_context"]) - }) } diff --git a/internal/analytics/log_client.go b/internal/analytics/log_client.go index ae99ca48..b2fb2ba7 100644 --- a/internal/analytics/log_client.go +++ b/internal/analytics/log_client.go @@ -16,13 +16,4 @@ func (c *LogClient) SendCommandRunEvent(properties map[string]interface{}) { func (c *LogClient) SendCommandCompletedEvent(outcome string) { log.Printf("SendCommandCompletedEvent, outcome: %v", outcome) } -func (c *LogClient) SendSetupStepStartedEvent(step string) { - log.Printf("SendSetupStepStartedEvent, step: %v", step) -} -func (c *LogClient) SendSetupSDKSelectedEvent(sdk string) { - log.Printf("SendSetupSDKSelectedEvent, sdk: %v", sdk) -} -func (c *LogClient) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { - log.Printf("SendSetupFlagToggledEvent, count: %v", count) -} func (a *LogClient) Wait() {} diff --git a/internal/analytics/mock.go b/internal/analytics/mock.go index 88e8870f..2c1a9c02 100644 --- a/internal/analytics/mock.go +++ b/internal/analytics/mock.go @@ -28,33 +28,4 @@ func (m *MockTracker) SendCommandCompletedEvent(outcome string) { ) } -func (m *MockTracker) SendSetupStepStartedEvent(step string) { - m.sendEvent( - "CLI Setup Step Started", - map[string]interface{}{ - "step": step, - }, - ) -} - -func (m *MockTracker) SendSetupSDKSelectedEvent(sdk string) { - m.sendEvent( - "CLI Setup SDK Selected", - map[string]interface{}{ - "sdk": sdk, - }, - ) -} - -func (m *MockTracker) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { - m.sendEvent( - "CLI Setup Flag Toggled", - map[string]interface{}{ - "on": on, - "count": count, - "duration_ms": duration_ms, - }, - ) -} - func (a *MockTracker) Wait() {} diff --git a/internal/analytics/noop_client.go b/internal/analytics/noop_client.go index d304e09b..5083fe52 100644 --- a/internal/analytics/noop_client.go +++ b/internal/analytics/noop_client.go @@ -10,9 +10,6 @@ func (fn NoopClientFn) Tracker() TrackerFn { type NoopClient struct{} -func (c *NoopClient) SendCommandRunEvent(properties map[string]interface{}) {} -func (c *NoopClient) SendCommandCompletedEvent(outcome string) {} -func (c *NoopClient) SendSetupStepStartedEvent(step string) {} -func (c *NoopClient) SendSetupSDKSelectedEvent(sdk string) {} -func (c *NoopClient) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) {} -func (a *NoopClient) Wait() {} +func (c *NoopClient) SendCommandRunEvent(properties map[string]interface{}) {} +func (c *NoopClient) SendCommandCompletedEvent(outcome string) {} +func (a *NoopClient) Wait() {} diff --git a/internal/analytics/tracker.go b/internal/analytics/tracker.go index f54bc4fd..8b09d4f2 100644 --- a/internal/analytics/tracker.go +++ b/internal/analytics/tracker.go @@ -5,8 +5,5 @@ type TrackerFn func(accessToken string, baseURI string, optOut bool) Tracker type Tracker interface { SendCommandRunEvent(properties map[string]interface{}) SendCommandCompletedEvent(outcome string) - SendSetupStepStartedEvent(step string) - SendSetupSDKSelectedEvent(sdk string) - SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) Wait() }