Skip to content

Commit f17e4c7

Browse files
authored
Find by glob pattern (#1)
1 parent bb9d512 commit f17e4c7

11 files changed

Lines changed: 265 additions & 41 deletions

File tree

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Go
20+
uses: actions/setup-go@v5
21+
with:
22+
go-version-file: go.mod
23+
cache: true
24+
25+
- name: Test
26+
run: go test ./...

.github/workflows/release.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ jobs:
2020
- name: Set up Go
2121
uses: actions/setup-go@v5
2222
with:
23-
go-version: stable
23+
go-version-file: go.mod
2424
cache: true
2525

26+
- name: Test
27+
run: go test ./...
28+
2629
- name: Run GoReleaser
2730
uses: goreleaser/goreleaser-action@v6
2831
with:

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,39 @@ graphql-cli mutate -e production 'mutation { createUser(name: "test") { id } }'
8181
graphql-cli mutate -e production -f mutation.graphql -v '{"name": "test"}'
8282
```
8383

84+
### `update` — Update an existing endpoint
85+
86+
```bash
87+
graphql-cli update <name> [--url <url>] [-d <description>] [--header key=value]
88+
```
89+
90+
**Examples:**
91+
92+
```bash
93+
graphql-cli update production --url https://api.example.com/v2/graphql
94+
graphql-cli update production --header "Authorization=Bearer new-token"
95+
graphql-cli update production --url https://new-url.com/graphql --header "X-Custom=value" -d "Updated endpoint"
96+
```
97+
8498
### `find` — Search schema definitions
8599

86100
```bash
87-
graphql-cli find -e <endpoint> [keyword] [--query] [--mutation] [--type] [--input] [--enum]
101+
graphql-cli find -e <endpoint> [keyword] [--query] [--mutation] [--type] [--input] [--enum] [--detail]
88102
```
89103

104+
By default, only names are shown. Use `--detail` to display full definitions with fields and arguments.
105+
106+
The keyword supports glob syntax (`*`, `?`, `[...]`). Without glob characters, it matches as a substring (e.g., `user` matches `getUser`, `UserInput`).
107+
90108
**Examples:**
91109

92110
```bash
93111
graphql-cli find -e production user
112+
graphql-cli find -e production "get*"
94113
graphql-cli find -e production user --query
95114
graphql-cli find -e production --mutation
96115
graphql-cli find -e production status --enum
116+
graphql-cli find -e production user --detail
97117
```
98118

99119
### `login` — Authenticate with an endpoint

SKILL.md

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A skill for managing GraphQL endpoints and executing operations using `graphql-c
1515
## Capabilities
1616

1717
- Add and manage multiple GraphQL endpoints (remote URL or local schema file)
18+
- Update existing endpoint URL, headers, or description
1819
- Authenticate with endpoints (Bearer token, Basic auth, custom header)
1920
- Execute GraphQL queries and mutations
2021
- Explore and search GraphQL schemas by keyword
@@ -54,36 +55,42 @@ graphql-cli add production --url https://api.example.com/graphql --description "
5455
graphql-cli add local --schema-file ./testdata/schema.graphql --description "Local schema"
5556
```
5657

57-
### 2. List endpoints
58+
### 2. Update an endpoint
59+
60+
```bash
61+
graphql-cli update <name> --url <new-url> [--description "desc"] [--header "Key=Value"]
62+
```
63+
64+
Example:
65+
```bash
66+
graphql-cli update production --url https://api.example.com/v2/graphql
67+
graphql-cli update production --header "Authorization=Bearer new-token" -d "Updated prod API"
68+
```
69+
70+
Headers are merged — existing headers not specified in the update are preserved.
71+
72+
### 3. List endpoints
5873

5974
```bash
6075
graphql-cli list # names and URLs
6176
graphql-cli list --detail # includes headers (masked) and auth status
6277
```
6378

64-
### 3. Authenticate
79+
### 4. Authenticate
6580

6681
```bash
67-
# Interactive (prompts for auth type and credentials)
68-
graphql-cli login <endpoint>
69-
70-
# Non-interactive
7182
graphql-cli login <endpoint> --type token --token "my-api-key"
7283
graphql-cli login <endpoint> --type basic --user admin --pass secret
7384
graphql-cli login <endpoint> --type header --key X-API-Key --value "key123"
85+
graphql-cli login -e production --type token --token "my-token"
7486

7587
# Remove credentials
7688
graphql-cli logout <endpoint>
7789
```
7890

7991
Credentials are stored in the OS keyring (macOS Keychain, Windows Credential Manager, GNOME Keyring) with a plaintext file fallback.
8092

81-
You can also specify the endpoint via `-e`:
82-
```bash
83-
graphql-cli login -e production --type token --token "my-token"
84-
```
85-
86-
### 4. Execute a query
93+
### 5. Execute a query
8794

8895
```bash
8996
graphql-cli query '<graphql-query>' -e <endpoint>
@@ -92,20 +99,28 @@ graphql-cli query '{ user(id: "1") { name } }' -e <endpoint> -v '{"id": "1"}'
9299
graphql-cli query '{ me { name } }' -e <endpoint> -H "Authorization=Bearer token"
93100
```
94101

95-
### 5. Execute a mutation
102+
### 6. Execute a mutation
96103

97104
```bash
98105
graphql-cli mutate '<graphql-mutation>' -e <endpoint>
99106
graphql-cli mutate -f mutation.graphql -e <endpoint> -v '{"name": "test"}'
100107
graphql-cli mutate 'mutation { createUser(name: "test") { id } }' -e <endpoint>
101108
```
102109

103-
### 6. Explore the schema
110+
### 7. Explore the schema
104111

105112
```bash
106-
# Search all definitions
113+
# Search all definitions (names only by default)
107114
graphql-cli find <keyword> -e <endpoint>
108115

116+
# Keyword supports glob syntax (*, ?, [...])
117+
# Without glob characters, matches as substring
118+
graphql-cli find "get*" -e <endpoint>
119+
graphql-cli find "User?" -e <endpoint>
120+
121+
# Show full definitions with fields and arguments
122+
graphql-cli find <keyword> -e <endpoint> --detail
123+
109124
# Narrow by kind
110125
graphql-cli find user -e <endpoint> --query # Query fields only
111126
graphql-cli find user -e <endpoint> --mutation # Mutation fields only
@@ -144,12 +159,19 @@ graphql-cli query '{ users { id name } }' -e prod 2>/dev/null | jq '.users[0]'
144159

145160
### Explore before querying
146161
```bash
147-
# First, find what queries are available
162+
# First, find what queries are available (names only)
148163
graphql-cli find -e prod --query
149164

150-
# Then find the input types needed
151-
graphql-cli find CreateUser -e prod --input
165+
# Then use --detail to see full definitions with fields and arguments
166+
graphql-cli find user -e prod --query --detail
167+
168+
# Find the input types needed
169+
graphql-cli find CreateUser -e prod --input --detail
152170

153171
# Then execute
154172
graphql-cli mutate 'mutation { createUser(input: {name: "Alice", email: "alice@example.com"}) { id } }' -e prod
155-
```
173+
```
174+
175+
## Guidelines
176+
177+
- **Always use `find` without `--detail` first** to get an overview of matching names, then use `find --detail` on specific results to see full definitions with fields and arguments. This avoids overwhelming output when schemas are large.

cmd/find.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@ var findCmd = &cobra.Command{
1616
Long: `Search for types, queries, mutations, inputs, and enums in the GraphQL schema.
1717
Use flags to narrow the search scope.
1818
19+
The keyword supports glob syntax (*, ?, [...]). Without glob characters,
20+
it matches as a substring (e.g., "user" matches "getUser", "UserInput").
21+
1922
Examples:
20-
graphql-cli find user
21-
graphql-cli find user --query
22-
graphql-cli find --mutation
23-
graphql-cli find user --type --input
24-
graphql-cli find status --enum`,
23+
graphql-cli find user # substring match
24+
graphql-cli find "get*" # glob: starts with "get"
25+
graphql-cli find "User?" # glob: "User" + one char
26+
graphql-cli find "{createUser,CreateUserInput}" # glob: exact alternatives
27+
graphql-cli find "[A-Z]*Input" # glob: capitalized, ends with "Input"
28+
graphql-cli find user --query # only Query fields
29+
graphql-cli find --mutation # list all mutations
30+
graphql-cli find user --type --input # types and inputs
31+
graphql-cli find status --enum # enums only
32+
graphql-cli find user --detail # show full definitions`,
2533
Args: cobra.MaximumNArgs(1),
2634
PreRunE: requireEndpoint,
2735
RunE: runFind,
@@ -33,6 +41,7 @@ var (
3341
findType bool
3442
findInput bool
3543
findEnum bool
44+
findDetail bool
3645
)
3746

3847
func init() {
@@ -41,6 +50,7 @@ func init() {
4150
findCmd.Flags().BoolVar(&findType, "type", false, "search only Object/Interface/Union/Scalar types")
4251
findCmd.Flags().BoolVar(&findInput, "input", false, "search only Input types")
4352
findCmd.Flags().BoolVar(&findEnum, "enum", false, "search only Enum types")
53+
findCmd.Flags().BoolVar(&findDetail, "detail", false, "show fields and arguments")
4454
rootCmd.AddCommand(findCmd)
4555
}
4656

@@ -78,7 +88,7 @@ func runFind(cmd *cobra.Command, args []string) error {
7888
return err
7989
}
8090

81-
printer.PrintFindResults(results)
91+
printer.PrintFindResults(results, findDetail)
8292

8393
return nil
8494
}

cmd/update.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/looplj/graphql-cli/internal/config"
10+
)
11+
12+
var updateCmd = &cobra.Command{
13+
Use: "update <name>",
14+
Short: "Update an existing endpoint's URL or headers",
15+
Long: `Update an existing GraphQL endpoint configuration.
16+
17+
Examples:
18+
graphql-cli update production --url https://api.example.com/v2/graphql
19+
graphql-cli update production --header "Authorization=Bearer new-token"
20+
graphql-cli update production --url https://new-url.com/graphql --header "X-Custom=value"`,
21+
Args: cobra.ExactArgs(1),
22+
RunE: runUpdate,
23+
}
24+
25+
var (
26+
updateURL string
27+
updateDescription string
28+
updateHeaders []string
29+
)
30+
31+
func init() {
32+
updateCmd.Flags().StringVar(&updateURL, "url", "", "new GraphQL endpoint URL")
33+
updateCmd.Flags().StringVarP(&updateDescription, "description", "d", "", "new endpoint description")
34+
updateCmd.Flags().StringSliceVar(&updateHeaders, "header", nil, "HTTP headers to add/update (key=value), can be specified multiple times")
35+
rootCmd.AddCommand(updateCmd)
36+
}
37+
38+
func runUpdate(cmd *cobra.Command, args []string) error {
39+
name := args[0]
40+
41+
var urlPtr *string
42+
43+
if cmd.Flags().Changed("url") {
44+
u, err := url.Parse(updateURL)
45+
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
46+
return fmt.Errorf("invalid endpoint URL %q: must be a valid http or https URL", updateURL)
47+
}
48+
49+
urlPtr = &updateURL
50+
}
51+
52+
var descPtr *string
53+
if cmd.Flags().Changed("description") {
54+
descPtr = &updateDescription
55+
}
56+
57+
headers := make(map[string]string)
58+
59+
for _, h := range updateHeaders {
60+
k, v, ok := parseHeader(h)
61+
if !ok {
62+
return fmt.Errorf("invalid header format %q, expected key=value", h)
63+
}
64+
65+
headers[k] = v
66+
}
67+
68+
if urlPtr == nil && descPtr == nil && len(headers) == 0 {
69+
return fmt.Errorf("must specify at least one of --url, --description, or --header")
70+
}
71+
72+
cfg, err := config.Load(cfgFile)
73+
if err != nil {
74+
return err
75+
}
76+
77+
if err := cfg.UpdateEndpoint(name, urlPtr, descPtr, headers); err != nil {
78+
return err
79+
}
80+
81+
if err := cfg.Save(cfgFile); err != nil {
82+
return err
83+
}
84+
85+
fmt.Printf("Updated endpoint %q\n", name)
86+
87+
return nil
88+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.0
44

55
require (
66
github.com/fatih/color v1.19.0
7+
github.com/gobwas/glob v0.2.3
78
github.com/spf13/cobra v1.10.2
89
github.com/vektah/gqlparser/v2 v2.5.32
910
github.com/zalando/go-keyring v0.2.7

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7c
1313
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
1414
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
1515
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
16+
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
17+
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
1618
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
1719
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
1820
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

internal/config/config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,32 @@ func (c *Config) GetEndpoint(name string) (*Endpoint, error) {
8585
return nil, fmt.Errorf("endpoint %q not found", name)
8686
}
8787

88+
func (c *Config) UpdateEndpoint(name string, url *string, description *string, headers map[string]string) error {
89+
for i := range c.Endpoints {
90+
if c.Endpoints[i].Name == name {
91+
if url != nil {
92+
c.Endpoints[i].URL = *url
93+
}
94+
95+
if description != nil {
96+
c.Endpoints[i].Description = *description
97+
}
98+
99+
for k, v := range headers {
100+
if c.Endpoints[i].Headers == nil {
101+
c.Endpoints[i].Headers = make(map[string]string)
102+
}
103+
104+
c.Endpoints[i].Headers[k] = v
105+
}
106+
107+
return nil
108+
}
109+
}
110+
111+
return fmt.Errorf("endpoint %q not found", name)
112+
}
113+
88114
func (c *Config) AddEndpoint(ep Endpoint) error {
89115
for _, e := range c.Endpoints {
90116
if e.Name == ep.Name {

0 commit comments

Comments
 (0)