Skip to content

Commit 29c1faf

Browse files
authored
Merge branch 'main' into REL-12752-tty-1
2 parents b0d06c4 + 5126f68 commit 29c1faf

12 files changed

Lines changed: 3588 additions & 318 deletions

File tree

.github/actions/publish/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ runs:
3232
using: composite
3333
steps:
3434
- name: Set up QEMU
35-
uses: docker/setup-qemu-action@v3
35+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
3636
- name: Setup Docker Buildx
37-
uses: docker/setup-buildx-action@v3
37+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
3838
with:
3939
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386
4040
- name: Set up goreleaser

.github/workflows/check-openapi-updates.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- uses: actions/checkout@v4
2222
- name: Send Slack notification
23-
uses: rtCamp/action-slack-notify@v2
23+
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2
2424
env:
2525
SLACK_CHANNEL: proj-cli
2626
SLACK_COLOR: ${{ job.status }}

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ jobs:
2424
run: go build .
2525

2626
- uses: actions/setup-python@v3
27-
- uses: pre-commit/action@v3.0.1
27+
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
2828
- name: test
2929
run: go test ./...

.github/workflows/release-please.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
release_created: ${{ steps.release.outputs.release_created }}
1515
tag_name: ${{ steps.release.outputs.tag_name }}
1616
steps:
17-
- uses: google-github-actions/release-please-action@v4
17+
- uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4
1818
id: release
1919
with:
2020
token: ${{secrets.GITHUB_TOKEN}}

cmd/resources/resource_cmds.go

Lines changed: 241 additions & 14 deletions
Large diffs are not rendered by default.

cmd/root.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import (
2222
flagscmd "github.com/launchdarkly/ldcli/cmd/flags"
2323
logincmd "github.com/launchdarkly/ldcli/cmd/login"
2424
memberscmd "github.com/launchdarkly/ldcli/cmd/members"
25+
sdkactivecmd "github.com/launchdarkly/ldcli/cmd/sdk_active"
2526
resourcecmd "github.com/launchdarkly/ldcli/cmd/resources"
27+
signupcmd "github.com/launchdarkly/ldcli/cmd/signup"
2628
sourcemapscmd "github.com/launchdarkly/ldcli/cmd/sourcemaps"
2729
"github.com/launchdarkly/ldcli/internal/analytics"
2830
"github.com/launchdarkly/ldcli/internal/config"
@@ -128,6 +130,7 @@ func NewRootCommand(
128130
"config",
129131
"help",
130132
"login",
133+
"signup",
131134
} {
132135
if cmd.HasParent() && cmd.Parent().Name() == name {
133136
cmd.DisableFlagParsing = true
@@ -250,6 +253,7 @@ func NewRootCommand(
250253
cmd.AddCommand(configCmd.Cmd())
251254
cmd.AddCommand(NewQuickStartCmd(analyticsTrackerFn, clients.EnvironmentsClient, clients.FlagsClient))
252255
cmd.AddCommand(logincmd.NewLoginCmd(clients.ResourcesClient))
256+
cmd.AddCommand(signupcmd.NewSignupCmd(analyticsTrackerFn))
253257
cmd.AddCommand(resourcecmd.NewResourcesCmd())
254258
cmd.AddCommand(devcmd.NewDevServerCmd(clients.ResourcesClient, analyticsTrackerFn, clients.DevClient))
255259
cmd.AddCommand(sourcemapscmd.NewSourcemapsCmd(clients.ResourcesClient, analyticsTrackerFn))
@@ -265,6 +269,9 @@ func NewRootCommand(
265269
if c.Name() == "members" {
266270
c.AddCommand(memberscmd.NewMembersInviteCmd(clients.ResourcesClient))
267271
}
272+
if c.Name() == "environments" {
273+
c.AddCommand(sdkactivecmd.NewSdkActiveCmd(clients.ResourcesClient))
274+
}
268275
}
269276

270277
rootCmd.Commands = append(rootCmd.Commands, configCmd)

cmd/sdk_active/sdk_active.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package sdk_active
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
11+
"github.com/launchdarkly/ldcli/cmd/cliflags"
12+
resourcescmd "github.com/launchdarkly/ldcli/cmd/resources"
13+
"github.com/launchdarkly/ldcli/cmd/validators"
14+
"github.com/launchdarkly/ldcli/internal/errors"
15+
"github.com/launchdarkly/ldcli/internal/output"
16+
"github.com/launchdarkly/ldcli/internal/resources"
17+
)
18+
19+
type sdkActiveResponse struct {
20+
Active bool `json:"active"`
21+
}
22+
23+
func NewSdkActiveCmd(client resources.Client) *cobra.Command {
24+
cmd := &cobra.Command{
25+
Args: validators.Validate(),
26+
Long: "Get SDK active status for an environment. Returns information about whether any SDKs have initialized in the given environment within the past seven days.",
27+
RunE: runGetSdkActive(client),
28+
Short: "Get SDK active status for an environment",
29+
Use: "get-sdk-active",
30+
}
31+
32+
cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate())
33+
initFlags(cmd)
34+
35+
return cmd
36+
}
37+
38+
const (
39+
sdkNameFlag = "sdk-name"
40+
sdkWrapperNameFlag = "sdk-wrapper-name"
41+
)
42+
43+
func runGetSdkActive(client resources.Client) func(*cobra.Command, []string) error {
44+
return func(cmd *cobra.Command, args []string) error {
45+
path, _ := url.JoinPath(
46+
viper.GetString(cliflags.BaseURIFlag),
47+
"api/v2/projects",
48+
viper.GetString(cliflags.ProjectFlag),
49+
"environments",
50+
viper.GetString(cliflags.EnvironmentFlag),
51+
"sdk-active",
52+
)
53+
54+
query := url.Values{}
55+
if v := viper.GetString(sdkNameFlag); v != "" {
56+
query.Set("sdk_name", v)
57+
}
58+
if v := viper.GetString(sdkWrapperNameFlag); v != "" {
59+
query.Set("sdk_wrapper_name", v)
60+
}
61+
62+
res, err := client.MakeRequest(
63+
viper.GetString(cliflags.AccessTokenFlag),
64+
"GET",
65+
path,
66+
"application/json",
67+
query,
68+
nil,
69+
false,
70+
)
71+
if err != nil {
72+
return output.NewCmdOutputError(err, cliflags.GetOutputKind(cmd))
73+
}
74+
75+
outputKind := cliflags.GetOutputKind(cmd)
76+
if outputKind == "json" {
77+
fmt.Fprint(cmd.OutOrStdout(), string(res)+"\n")
78+
return nil
79+
}
80+
81+
var resp sdkActiveResponse
82+
if err := json.Unmarshal(res, &resp); err != nil {
83+
return errors.NewError(err.Error())
84+
}
85+
86+
fmt.Fprintf(cmd.OutOrStdout(), "SDK active: %t\n", resp.Active)
87+
88+
return nil
89+
}
90+
}
91+
92+
func initFlags(cmd *cobra.Command) {
93+
cmd.Flags().String(cliflags.ProjectFlag, "", "The project key")
94+
_ = cmd.MarkFlagRequired(cliflags.ProjectFlag)
95+
_ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"})
96+
_ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag))
97+
98+
cmd.Flags().String(cliflags.EnvironmentFlag, "", "The environment key")
99+
_ = cmd.MarkFlagRequired(cliflags.EnvironmentFlag)
100+
_ = cmd.Flags().SetAnnotation(cliflags.EnvironmentFlag, "required", []string{"true"})
101+
_ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag))
102+
103+
cmd.Flags().String(sdkNameFlag, "", "Filter by SDK name (e.g. go-server-sdk, node-server-sdk)")
104+
_ = viper.BindPFlag(sdkNameFlag, cmd.Flags().Lookup(sdkNameFlag))
105+
106+
cmd.Flags().String(sdkWrapperNameFlag, "", "Filter by SDK wrapper name")
107+
_ = viper.BindPFlag(sdkWrapperNameFlag, cmd.Flags().Lookup(sdkWrapperNameFlag))
108+
}

cmd/sdk_active/sdk_active_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package sdk_active_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/launchdarkly/ldcli/cmd"
10+
"github.com/launchdarkly/ldcli/internal/analytics"
11+
"github.com/launchdarkly/ldcli/internal/resources"
12+
)
13+
14+
func TestGetSdkActive(t *testing.T) {
15+
mockClient := &resources.MockClient{
16+
Response: []byte(`{"active": true}`),
17+
}
18+
args := []string{
19+
"environments", "get-sdk-active",
20+
"--access-token", "abcd1234",
21+
"--project", "test-proj",
22+
"--environment", "test-env",
23+
}
24+
output, err := cmd.CallCmd(
25+
t,
26+
cmd.APIClients{
27+
ResourcesClient: mockClient,
28+
},
29+
analytics.NoopClientFn{}.Tracker(),
30+
args,
31+
)
32+
33+
require.NoError(t, err)
34+
assert.Equal(t, "SDK active: true\n", string(output))
35+
}
36+
37+
func TestGetSdkActiveJSON(t *testing.T) {
38+
mockClient := &resources.MockClient{
39+
Response: []byte(`{"active": true}`),
40+
}
41+
args := []string{
42+
"environments", "get-sdk-active",
43+
"--access-token", "abcd1234",
44+
"--project", "test-proj",
45+
"--environment", "test-env",
46+
"--output", "json",
47+
}
48+
output, err := cmd.CallCmd(
49+
t,
50+
cmd.APIClients{
51+
ResourcesClient: mockClient,
52+
},
53+
analytics.NoopClientFn{}.Tracker(),
54+
args,
55+
)
56+
57+
require.NoError(t, err)
58+
assert.Contains(t, string(output), `"active"`)
59+
}
60+
61+
func TestGetSdkActiveWithSdkNameFilter(t *testing.T) {
62+
mockClient := &resources.MockClient{
63+
Response: []byte(`{"active": true}`),
64+
}
65+
args := []string{
66+
"environments", "get-sdk-active",
67+
"--access-token", "abcd1234",
68+
"--project", "test-proj",
69+
"--environment", "test-env",
70+
"--sdk-name", "go-server-sdk",
71+
}
72+
output, err := cmd.CallCmd(
73+
t,
74+
cmd.APIClients{
75+
ResourcesClient: mockClient,
76+
},
77+
analytics.NoopClientFn{}.Tracker(),
78+
args,
79+
)
80+
81+
require.NoError(t, err)
82+
assert.Equal(t, "SDK active: true\n", string(output))
83+
}
84+
85+
func TestGetSdkActiveWithSdkWrapperNameFilter(t *testing.T) {
86+
mockClient := &resources.MockClient{
87+
Response: []byte(`{"active": false}`),
88+
}
89+
args := []string{
90+
"environments", "get-sdk-active",
91+
"--access-token", "abcd1234",
92+
"--project", "test-proj",
93+
"--environment", "test-env",
94+
"--sdk-wrapper-name", "flutter-client-sdk",
95+
}
96+
output, err := cmd.CallCmd(
97+
t,
98+
cmd.APIClients{
99+
ResourcesClient: mockClient,
100+
},
101+
analytics.NoopClientFn{}.Tracker(),
102+
args,
103+
)
104+
105+
require.NoError(t, err)
106+
assert.Equal(t, "SDK active: false\n", string(output))
107+
}
108+
109+
func TestGetSdkActiveMissingRequiredFlags(t *testing.T) {
110+
mockClient := &resources.MockClient{}
111+
args := []string{
112+
"environments", "get-sdk-active",
113+
"--access-token", "abcd1234",
114+
}
115+
_, err := cmd.CallCmd(
116+
t,
117+
cmd.APIClients{
118+
ResourcesClient: mockClient,
119+
},
120+
analytics.NoopClientFn{}.Tracker(),
121+
args,
122+
)
123+
124+
require.Error(t, err)
125+
assert.Contains(t, err.Error(), "required")
126+
}

cmd/signup/signup.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package signup
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
7+
"github.com/pkg/browser"
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
11+
cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics"
12+
"github.com/launchdarkly/ldcli/cmd/cliflags"
13+
"github.com/launchdarkly/ldcli/internal/analytics"
14+
)
15+
16+
func NewSignupCmd(analyticsTrackerFn analytics.TrackerFn) *cobra.Command {
17+
cmd := &cobra.Command{
18+
Long: "Open your browser to create a new LaunchDarkly account",
19+
PreRun: func(cmd *cobra.Command, args []string) {
20+
analyticsTrackerFn(
21+
viper.GetString(cliflags.AccessTokenFlag),
22+
viper.GetString(cliflags.BaseURIFlag),
23+
viper.GetBool(cliflags.AnalyticsOptOut),
24+
).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "signup", nil))
25+
},
26+
RunE: run,
27+
Short: "Create a new LaunchDarkly account",
28+
Use: "signup",
29+
}
30+
31+
return cmd
32+
}
33+
34+
func run(cmd *cobra.Command, args []string) error {
35+
signupURL, err := url.JoinPath(
36+
viper.GetString(cliflags.BaseURIFlag),
37+
"/signup",
38+
)
39+
if err != nil {
40+
return fmt.Errorf("failed to construct signup URL: %w", err)
41+
}
42+
43+
fmt.Fprintf(cmd.OutOrStdout(), "Opening your browser to %s to create a new LaunchDarkly account.\n", signupURL)
44+
fmt.Fprintln(cmd.OutOrStdout(), "If your browser does not open automatically, you can paste the above URL into your browser.")
45+
46+
err = browser.OpenURL(signupURL)
47+
if err != nil {
48+
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: failed to open browser automatically: %v\n", err)
49+
}
50+
51+
return nil
52+
}

cmd/signup/signup_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package signup
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/launchdarkly/ldcli/internal/analytics"
9+
)
10+
11+
func TestSignupCmd(t *testing.T) {
12+
t.Run("creates signup command with correct attributes", func(t *testing.T) {
13+
mockTracker := &analytics.MockTracker{}
14+
analyticsTrackerFn := func(accessToken string, baseURI string, optOut bool) analytics.Tracker {
15+
return mockTracker
16+
}
17+
18+
cmd := NewSignupCmd(analyticsTrackerFn)
19+
20+
assert.Equal(t, "signup", cmd.Use)
21+
assert.Equal(t, "Create a new LaunchDarkly account", cmd.Short)
22+
assert.Equal(t, "Open your browser to create a new LaunchDarkly account", cmd.Long)
23+
assert.NotNil(t, cmd.RunE)
24+
assert.NotNil(t, cmd.PreRun)
25+
})
26+
}

0 commit comments

Comments
 (0)