diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..f9ea770 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,31 @@ +name: Build & Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version: "1.27.x" + + - name: Build + run: go build ./... + + - name: Test + run: go test -race ./... + + - name: Vet + run: go vet ./... diff --git a/README.md b/README.md index 0efc46d..c5dcdf4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ +[![Build & Test](https://github.com/markmnl/fmsg-cli/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/markmnl/fmsg-cli/actions/workflows/build-test.yml?query=branch%3Amain) +[![Go 1.27+](https://img.shields.io/badge/Go-1.27%2B-00ADD8?logo=go&logoColor=white)](https://go.dev/dl/) + # fmsg-cli Command-line interface to [fmsg-webapi](https://github.com/markmnl/fmsg-webapi) fronting a fmsgd instance. ## Requirements -- Go 1.24 or newer +- Go 1.27 or newer ## Build diff --git a/cmd/draft.go b/cmd/draft.go index 9aa1e88..d67fc76 100644 --- a/cmd/draft.go +++ b/cmd/draft.go @@ -55,7 +55,7 @@ var draftCreateCmd = &cobra.Command{ } } - msg := map[string]interface{}{ + msg := map[string]any{ "from": user, "to": []string{recipient}, "version": 1, diff --git a/cmd/root.go b/cmd/root.go index ef82a93..dfd3638 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "slices" "unicode" "github.com/spf13/cobra" @@ -28,7 +29,7 @@ Before using any command, authenticate with: var jsonOutput bool // printJSON writes v to stdout as a single line of JSON. -func printJSON(v interface{}) error { +func printJSON(v any) error { enc := json.NewEncoder(os.Stdout) enc.SetEscapeHTML(false) return enc.Encode(v) @@ -40,10 +41,8 @@ func printJSON(v interface{}) error { // misinterpreting the leading dash as a shorthand flag. If "--" is already // present in os.Args the function is a no-op. func injectDashDash() { - for _, arg := range os.Args[1:] { - if arg == "--" { - return // user already supplied the separator - } + if slices.Contains(os.Args[1:], "--") { + return // user already supplied the separator } for i, arg := range os.Args[1:] { if len(arg) >= 2 && arg[0] == '-' && unicode.IsDigit(rune(arg[1])) { diff --git a/cmd/send.go b/cmd/send.go index a0ebbfd..11395b4 100644 --- a/cmd/send.go +++ b/cmd/send.go @@ -54,7 +54,7 @@ var sendCmd = &cobra.Command{ } // Build a draft payload. - msg := map[string]interface{}{ + msg := map[string]any{ "from": user, "to": []string{recipient}, "version": 1, diff --git a/cmd/sub_accounts.go b/cmd/sub_accounts.go index 873a9cd..340ffec 100644 --- a/cmd/sub_accounts.go +++ b/cmd/sub_accounts.go @@ -183,7 +183,7 @@ func splitCIDRs(raw string) []string { return nil } var out []string - for _, c := range strings.Split(raw, ",") { + for c := range strings.SplitSeq(raw, ",") { c = strings.TrimSpace(c) if c != "" { out = append(out, c) diff --git a/cmd/update.go b/cmd/update.go index 1a6f8bb..2f488de 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -53,7 +53,7 @@ and merged first — unchanged fields are preserved.)`, return fmt.Errorf("fetching current draft: %w", err) } - msg := map[string]interface{}{ + msg := map[string]any{ "from": user, "version": 1, } diff --git a/cmd/watch.go b/cmd/watch.go index 71d3975..6643c9a 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -62,7 +62,7 @@ Exit codes: 0 after an event (--once) or when stopped by Ctrl-C/--timeout; wanted := map[string]bool{} for _, e := range watchEvents { - for _, part := range strings.Split(e, ",") { + for part := range strings.SplitSeq(e, ",") { if part = strings.TrimSpace(part); part != "" { wanted[part] = true } diff --git a/go.mod b/go.mod index 0696e3f..800c115 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/markmnl/fmsg-cli -go 1.24 +go 1.27.0 require ( github.com/joho/godotenv v1.5.1 diff --git a/internal/api/client.go b/internal/api/client.go index 5e9f493..e649fd3 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -392,7 +392,7 @@ func (c *Client) SendMessage(id int64) (*SendMessageResponse, error) { // AddRecipients adds additional recipients to an existing message. func (c *Client) AddRecipients(id int64, addTo []string) (*AddRecipientsResponse, error) { - body, err := json.Marshal(map[string]interface{}{"add_to": addTo}) + body, err := json.Marshal(map[string]any{"add_to": addTo}) if err != nil { return nil, fmt.Errorf("encoding request: %w", err) } @@ -644,7 +644,7 @@ func (c *Client) GetSubAccount(agent string) (*SubAccount, error) { // CreateSubAccount derives a new sub-account and returns its plaintext API key. func (c *Client) CreateSubAccount(agent string, allowedCIDRs []string, keyExpiresAt string) (*SubAccount, error) { - payload, err := json.Marshal(map[string]interface{}{ + payload, err := json.Marshal(map[string]any{ "agent": agent, "allowed_cidrs": allowedCIDRs, "key_expires_at": keyExpiresAt, @@ -678,7 +678,7 @@ func (c *Client) CreateSubAccount(agent string, allowedCIDRs []string, keyExpire // UpdateSubAccountCIDRs replaces a sub-account's allowed CIDRs without rotating its key. func (c *Client) UpdateSubAccountCIDRs(agent string, allowedCIDRs []string) (*SubAccount, error) { - payload, err := json.Marshal(map[string]interface{}{ + payload, err := json.Marshal(map[string]any{ "allowed_cidrs": allowedCIDRs, }) if err != nil { @@ -710,7 +710,7 @@ func (c *Client) UpdateSubAccountCIDRs(agent string, allowedCIDRs []string) (*Su // RotateSubAccountKey rotates a sub-account's API key and returns the new plaintext key. func (c *Client) RotateSubAccountKey(agent, keyExpiresAt string) (*SubAccount, error) { - payload, err := json.Marshal(map[string]interface{}{ + payload, err := json.Marshal(map[string]any{ "key_expires_at": keyExpiresAt, }) if err != nil { diff --git a/internal/auth/manager_test.go b/internal/auth/manager_test.go index e6bd769..b8febc5 100644 --- a/internal/auth/manager_test.go +++ b/internal/auth/manager_test.go @@ -386,7 +386,7 @@ func TestEnvAPIKeyTokenIsCachedAcrossProcesses(t *testing.T) { defer srv.Close() // Two separate Managers stand in for two CLI processes. - for i := 0; i < 2; i++ { + for i := range 2 { m := NewManager(srv.URL) m.now = func() time.Time { return now } if _, err := m.AccessToken(context.Background(), false); err != nil { @@ -459,7 +459,7 @@ func TestEnvAPIKeyTokenCacheCanBeDisabled(t *testing.T) { }) })) defer srv.Close() - for i := 0; i < 2; i++ { + for range 2 { m := NewManager(srv.URL) m.now = func() time.Time { return now } if _, err := m.AccessToken(context.Background(), false); err != nil { diff --git a/internal/auth/store.go b/internal/auth/store.go index c009408..e75cb47 100644 --- a/internal/auth/store.go +++ b/internal/auth/store.go @@ -16,7 +16,7 @@ type Credentials struct { APIKey string `json:"api_key,omitempty"` AccessToken string `json:"access_token,omitempty"` TokenType string `json:"token_type,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` + ExpiresAt time.Time `json:"expires_at"` User string `json:"user,omitempty"` APIURL string `json:"api_url,omitempty"` }