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/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() } 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)) - }) - } -}