Skip to content

Commit f4cb3ac

Browse files
authored
chore: add components commands (#69)
1 parent 667a2c8 commit f4cb3ac

5 files changed

Lines changed: 200 additions & 3 deletions

File tree

cmd/components.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/loops-so/loops-go"
7+
"github.com/loops-so/cli/internal/config"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func runComponentsGet(cfg *config.Config, id string) (*loops.Component, error) {
12+
return newAPIClient(cfg).GetComponent(id)
13+
}
14+
15+
func runComponentsList(cfg *config.Config, params loops.PaginationParams) ([]loops.Component, error) {
16+
client := newAPIClient(cfg)
17+
if params.Cursor != "" {
18+
components, _, err := client.ListComponents(params)
19+
return components, err
20+
}
21+
return loops.Paginate(func(cursor string) ([]loops.Component, *loops.Pagination, error) {
22+
return client.ListComponents(loops.PaginationParams{
23+
PerPage: params.PerPage,
24+
Cursor: cursor,
25+
})
26+
})
27+
}
28+
29+
var componentsCmd = &cobra.Command{
30+
Use: "components",
31+
Short: "Manage components",
32+
Hidden: true,
33+
}
34+
35+
var componentsListCmd = &cobra.Command{
36+
Use: "list",
37+
Short: "List components",
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
if err := validatePickFlags(cmd); err != nil {
40+
return err
41+
}
42+
43+
cfg, err := loadConfig()
44+
if err != nil {
45+
return err
46+
}
47+
48+
components, err := runComponentsList(cfg, paginationParams(cmd))
49+
if err != nil {
50+
return err
51+
}
52+
53+
if isJSONOutput() {
54+
if components == nil {
55+
components = []loops.Component{}
56+
}
57+
return printJSON(cmd.OutOrStdout(), components)
58+
}
59+
60+
if len(components) == 0 {
61+
fmt.Fprintln(cmd.OutOrStdout(), "No components found.")
62+
return nil
63+
}
64+
65+
headers := []string{"ID", "NAME"}
66+
rows := make([][]string, 0, len(components))
67+
for _, c := range components {
68+
rows = append(rows, []string{c.ComponentID, c.Name})
69+
}
70+
71+
if isPicking(cmd) {
72+
return runPicker(headers, rows, []pickBinding{
73+
copyColumnBinding("enter", "copy id", "component ID", rows, 0, cmd.OutOrStdout()),
74+
})
75+
}
76+
77+
t := newStyledTable(cmd.OutOrStdout(), headers...)
78+
for _, r := range rows {
79+
t.Row(r...)
80+
}
81+
return t.Render()
82+
},
83+
}
84+
85+
var componentsGetCmd = &cobra.Command{
86+
Use: "get <id>",
87+
Short: "Get a component",
88+
Args: cobra.ExactArgs(1),
89+
RunE: func(cmd *cobra.Command, args []string) error {
90+
cfg, err := loadConfig()
91+
if err != nil {
92+
return err
93+
}
94+
95+
c, err := runComponentsGet(cfg, args[0])
96+
if err != nil {
97+
return err
98+
}
99+
100+
if isJSONOutput() {
101+
return printJSON(cmd.OutOrStdout(), c)
102+
}
103+
104+
t := newStyledTable(cmd.OutOrStdout(), "FIELD", "VALUE")
105+
t.Row("componentId", c.ComponentID)
106+
t.Row("name", c.Name)
107+
if err := t.Render(); err != nil {
108+
return err
109+
}
110+
111+
fmt.Fprintln(cmd.OutOrStdout())
112+
return renderLMX(cmd.OutOrStdout(), c.LMX)
113+
},
114+
}
115+
116+
func init() {
117+
addPaginationFlags(componentsListCmd)
118+
addPickFlag(componentsListCmd)
119+
componentsCmd.AddCommand(componentsListCmd)
120+
componentsCmd.AddCommand(componentsGetCmd)
121+
rootCmd.AddCommand(componentsCmd)
122+
}

cmd/components_get_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
)
7+
8+
func TestRunComponentsGet(t *testing.T) {
9+
body := `{
10+
"success": true,
11+
"componentId": "cmpt_abc123",
12+
"name": "Header",
13+
"lmx": "<H1>Hello</H1>"
14+
}`
15+
16+
t.Run("returns the component", func(t *testing.T) {
17+
serveJSON(t, http.StatusOK, body)
18+
c, err := runComponentsGet(cfg(t), "cmpt_abc123")
19+
if err != nil {
20+
t.Fatalf("unexpected error: %v", err)
21+
}
22+
if c.ComponentID != "cmpt_abc123" {
23+
t.Errorf("ComponentID = %q, want cmpt_abc123", c.ComponentID)
24+
}
25+
if c.Name != "Header" {
26+
t.Errorf("Name = %q, want Header", c.Name)
27+
}
28+
if c.LMX != "<H1>Hello</H1>" {
29+
t.Errorf("LMX = %q, want <H1>Hello</H1>", c.LMX)
30+
}
31+
})
32+
33+
t.Run("returns error on non-200 response", func(t *testing.T) {
34+
serveJSON(t, http.StatusNotFound, `{"success":false,"message":"Component not found"}`)
35+
_, err := runComponentsGet(cfg(t), "cmpt_missing")
36+
if err == nil {
37+
t.Fatal("expected error, got nil")
38+
}
39+
})
40+
}

cmd/components_list_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/loops-so/loops-go"
8+
)
9+
10+
func TestRunComponentsList(t *testing.T) {
11+
t.Run("returns components", func(t *testing.T) {
12+
serveJSON(t, http.StatusOK, `{"pagination":{"nextCursor":""},"data":[{"componentId":"cmpt_1","name":"Header","lmx":"<H1>Hello</H1>"}]}`)
13+
components, err := runComponentsList(cfg(t), loops.PaginationParams{})
14+
if err != nil {
15+
t.Fatalf("unexpected error: %v", err)
16+
}
17+
if len(components) != 1 {
18+
t.Fatalf("expected 1 component, got %d", len(components))
19+
}
20+
if components[0].ComponentID != "cmpt_1" {
21+
t.Errorf("ComponentID = %q, want cmpt_1", components[0].ComponentID)
22+
}
23+
if components[0].Name != "Header" {
24+
t.Errorf("Name = %q, want Header", components[0].Name)
25+
}
26+
})
27+
28+
t.Run("returns error on api failure", func(t *testing.T) {
29+
serveJSON(t, http.StatusUnauthorized, `{"error":"unauthorized"}`)
30+
_, err := runComponentsList(cfg(t), loops.PaginationParams{})
31+
if err == nil {
32+
t.Fatal("expected error, got nil")
33+
}
34+
})
35+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/atotto/clipboard v0.1.4
1010
github.com/charmbracelet/colorprofile v0.4.2
1111
github.com/charmbracelet/x/term v0.2.2
12-
github.com/loops-so/loops-go v0.1.1
12+
github.com/loops-so/loops-go v0.1.2
1313
github.com/spf13/cobra v1.10.2
1414
github.com/zalando/go-keyring v0.2.6
1515
golang.org/x/term v0.41.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
299299
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
300300
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
301301
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
302-
github.com/loops-so/loops-go v0.1.1 h1:BV+ZIr9RwuCi0maiJcYvYCliXws8tCmj5hW9sB6epC0=
303-
github.com/loops-so/loops-go v0.1.1/go.mod h1:BDzBhAn/4e2QSKXrpXufIpSuH8xUPv9oa+hazH01ejE=
302+
github.com/loops-so/loops-go v0.1.2 h1:ROaDb9LEQvRlZupa1w+OqBDfNKo6agIZhoDYv7hjkes=
303+
github.com/loops-so/loops-go v0.1.2/go.mod h1:BDzBhAn/4e2QSKXrpXufIpSuH8xUPv9oa+hazH01ejE=
304304
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
305305
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
306306
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=

0 commit comments

Comments
 (0)