Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0-alpha.18"
".": "0.1.0-alpha.19"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 15
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/stainless%2Fstainless-v0-ddcc03297d5649ca543ccff5be72ca1c148696cdddf63cb640e3f8a9b86ab59e.yml
openapi_spec_hash: 2870606e51060e9080104b1089f28b83
config_hash: 94d0bc0af5fa718b5ab095e1d934e834
config_hash: 3390d0ac1f2bb2f8e759f7758d1312c7
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.1.0-alpha.19 (2025-06-20)

Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/stainless-api/stainless-api-cli/compare/v0.1.0-alpha.18...v0.1.0-alpha.19)

### Chores

* bump go package version ([abc91cb](https://github.com/stainless-api/stainless-api-cli/commit/abc91cb66f1e668c1e55f867ef2fbe4e22d6b61c))
* **internal:** codegen related update ([e84af71](https://github.com/stainless-api/stainless-api-cli/commit/e84af710aeaec0674afcef3d4eba4e5d3bbe5e71))

## 0.1.0-alpha.18 (2025-06-19)

Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/stainless-api/stainless-api-cli/compare/v0.1.0-alpha.17...v0.1.0-alpha.18)
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ go 1.23.0
toolchain go1.23.10

require (
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/huh v0.7.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/stainless-api/stainless-api-go v0.8.0
github.com/stainless-api/stainless-api-go v0.9.0
github.com/tidwall/gjson v1.17.0
github.com/tidwall/pretty v1.2.1
github.com/tidwall/sjson v1.2.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stainless-api/stainless-api-go v0.8.0 h1:GD3U3B20zG9FW/O2I//6zqiMJSpw6AterUn0x08QU5c=
github.com/stainless-api/stainless-api-go v0.8.0/go.mod h1:9Q2t8xq6EFgw8HYOsVxqKEfSDVe9eqCoh1zC0HMRwTY=
github.com/stainless-api/stainless-api-go v0.9.0 h1:HlPcGQT6FX1/oPhpJTo46uBAjgwmUdzW7S97fMD0Gp0=
github.com/stainless-api/stainless-api-go v0.9.0/go.mod h1:9Q2t8xq6EFgw8HYOsVxqKEfSDVe9eqCoh1zC0HMRwTY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ func getAPICommandContextWithWorkspaceDefaults(cmd *cli.Command) (*apiCommandCon
if err != nil {
return nil, fmt.Errorf("failed to load OpenAPI spec from workspace config: %v", err)
}
jsonflag.Register(jsonflag.Body, "revision.openapi\\.yml.content", string(content))
jsonflag.Mutate(jsonflag.Body, "revision.openapi\\.yml.content", string(content))
}

if !cmd.IsSet("stainless-config") && !cmd.IsSet("config") && config.StainlessConfig != "" {
Expand All @@ -615,7 +615,7 @@ func getAPICommandContextWithWorkspaceDefaults(cmd *cli.Command) (*apiCommandCon
if err != nil {
return nil, fmt.Errorf("failed to load Stainless config from workspace config: %v", err)
}
jsonflag.Register(jsonflag.Body, "revision.openapi\\.stainless\\.yml.content", string(content))
jsonflag.Mutate(jsonflag.Body, "revision.openapi\\.stainless\\.yml.content", string(content))
}
}
return cc, err
Expand Down
4 changes: 4 additions & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ var Command = cli.Command{
Name: "debug",
Usage: "Enable debug logging",
},
&cli.StringFlag{
Name: "base-url",
Usage: "Override the base URL for API requests",
},
},
Commands: []*cli.Command{
{
Expand Down
14 changes: 6 additions & 8 deletions pkg/cmd/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ var projectsList = cli.Command{
}

func handleProjectsCreate(ctx context.Context, cmd *cli.Command) error {
cc := getAPICommandContext(cmd)

// Define available target languages
availableTargets := []huh.Option[string]{
huh.NewOption("TypeScript", "typescript").Selected(true),
Expand Down Expand Up @@ -184,7 +186,7 @@ func handleProjectsCreate(ctx context.Context, cmd *cli.Command) error {
Info("Creating a new project...")

// Fetch available organizations for suggestions
orgs := fetchUserOrgs(ctx)
orgs := fetchUserOrgs(cc.client, ctx)

// Auto-fill with first organization if org is empty and orgs are available
if org == "" && len(orgs) > 0 {
Expand Down Expand Up @@ -294,20 +296,18 @@ func handleProjectsCreate(ctx context.Context, cmd *cli.Command) error {
content, err := os.ReadFile(openAPISpec)
if err == nil {
// Inject the actual file content into the project creation payload
jsonflag.Register(jsonflag.Body, "revision.openapi\\.yml.content", string(content))
jsonflag.Mutate(jsonflag.Body, "revision.openapi\\.yml.content", string(content))
}
}

if stainlessConfig != "" {
content, err := os.ReadFile(stainlessConfig)
if err == nil {
// Inject the actual file content into the project creation payload
jsonflag.Register(jsonflag.Body, "revision.openapi\\.stainless\\.yml.content", string(content))
jsonflag.Mutate(jsonflag.Body, "revision.openapi\\.stainless\\.yml.content", string(content))
}
}

// Use the original logic - let the JSONFlag middleware handle parameter construction
cc := getAPICommandContext(cmd)
params := stainlessv0.ProjectNewParams{}
res, err := cc.client.Projects.New(
context.TODO(),
Expand Down Expand Up @@ -402,9 +402,7 @@ func handleProjectsList(ctx context.Context, cmd *cli.Command) error {
}

// fetchUserOrgs retrieves the list of organizations the user has access to
func fetchUserOrgs(ctx context.Context) []string {
client := stainlessv0.NewClient(getClientOptions()...)

func fetchUserOrgs(client stainlessv0.Client, ctx context.Context) []string {
res, err := client.Orgs.List(ctx)
if err != nil {
// Return empty slice if we can't fetch orgs
Expand Down
26 changes: 16 additions & 10 deletions pkg/cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,20 @@ import (
"golang.org/x/term"
)

func getDefaultRequestOptions() []option.RequestOption {
return append(
[]option.RequestOption{
option.WithHeader("X-Stainless-Lang", "cli"),
option.WithHeader("X-Stainless-Runtime", "cli"),
},
getClientOptions()...,
)
func getDefaultRequestOptions(cmd *cli.Command) []option.RequestOption {
opts := []option.RequestOption{
option.WithHeader("X-Stainless-Lang", "cli"),
option.WithHeader("X-Stainless-Runtime", "cli"),
}

// Override base URL if the --base-url flag is provided
if baseURL := cmd.String("base-url"); baseURL != "" {
opts = append(opts, option.WithBaseURL(baseURL))
}

opts = append(opts, getClientOptions()...)

return opts
}

type apiCommandContext struct {
Expand All @@ -47,7 +53,7 @@ func (c apiCommandContext) AsMiddleware() option.Middleware {
var header = []byte("{}")

// Apply JSON flag mutations
body, query, header, err := jsonflag.Apply(body, query, header)
body, query, header, err := jsonflag.ApplyMutations(body, query, header)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -130,7 +136,7 @@ func (c apiCommandContext) AsMiddleware() option.Middleware {
}

func getAPICommandContext(cmd *cli.Command) *apiCommandContext {
client := stainlessv0.NewClient(getDefaultRequestOptions()...)
client := stainlessv0.NewClient(getDefaultRequestOptions(cmd)...)
return &apiCommandContext{client, cmd}
}

Expand Down
39 changes: 19 additions & 20 deletions pkg/jsonflag/json_flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ type JSONConfig struct {
SetValue interface{}
}

type JsonValueCreator[T any] struct{}
type JSONValueCreator[T any] struct{}

func (c JsonValueCreator[T]) Create(val T, dest *T, config JSONConfig) cli.Value {
func (c JSONValueCreator[T]) Create(val T, dest *T, config JSONConfig) cli.Value {
*dest = val
return &jsonValue[T]{
destination: dest,
config: config,
}
}

func (c JsonValueCreator[T]) ToString(val T) string {
func (c JSONValueCreator[T]) ToString(val T) string {
switch v := any(val).(type) {
case string:
if v == "" {
Expand Down Expand Up @@ -59,13 +59,13 @@ func (v *jsonValue[T]) Set(val string) error {
if v.config.SetValue != nil {
// For boolean flags with SetValue, register the configured value
if _, isBool := any(parsed).(bool); isBool {
globalRegistry.Register(v.config.Kind, v.config.Path, v.config.SetValue)
globalRegistry.Mutate(v.config.Kind, v.config.Path, v.config.SetValue)
*v.destination = any(true).(T) // Set the flag itself to true
return nil
}
// For any flags with SetValue, register the configured value
if _, isAny := any(parsed).(interface{}); isAny {
globalRegistry.Register(v.config.Kind, v.config.Path, v.config.SetValue)
globalRegistry.Mutate(v.config.Kind, v.config.Path, v.config.SetValue)
*v.destination = any(v.config.SetValue).(T)
return nil
}
Expand Down Expand Up @@ -123,7 +123,7 @@ func (v *jsonValue[T]) Set(val string) error {
}

*v.destination = parsed
globalRegistry.Register(v.config.Kind, v.config.Path, parsed)
globalRegistry.Mutate(v.config.Kind, v.config.Path, parsed)
return err
}

Expand Down Expand Up @@ -173,18 +173,18 @@ func (v *jsonValue[T]) IsBoolFlag() bool {
return v.config.SetValue != nil
}

// JsonDateValueCreator is a specialized creator for date-only values
type JsonDateValueCreator struct{}
// JSONDateValueCreator is a specialized creator for date-only values
type JSONDateValueCreator struct{}

func (c JsonDateValueCreator) Create(val time.Time, dest *time.Time, config JSONConfig) cli.Value {
func (c JSONDateValueCreator) Create(val time.Time, dest *time.Time, config JSONConfig) cli.Value {
*dest = val
return &jsonDateValue{
destination: dest,
config: config,
}
}

func (c JsonDateValueCreator) ToString(val time.Time) string {
func (c JSONDateValueCreator) ToString(val time.Time) string {
return val.Format("2006-01-02")
}

Expand Down Expand Up @@ -220,7 +220,7 @@ func (v *jsonDateValue) Set(val string) error {
}

*v.destination = timeVal
globalRegistry.Register(v.config.Kind, v.config.Path, timeVal.Format("2006-01-02"))
globalRegistry.Mutate(v.config.Kind, v.config.Path, timeVal.Format("2006-01-02"))
return nil
}

Expand All @@ -242,14 +242,6 @@ func (v *jsonDateValue) IsBoolFlag() bool {
return false
}

type JSONStringFlag = cli.FlagBase[string, JSONConfig, JsonValueCreator[string]]
type JSONBoolFlag = cli.FlagBase[bool, JSONConfig, JsonValueCreator[bool]]
type JSONIntFlag = cli.FlagBase[int, JSONConfig, JsonValueCreator[int]]
type JSONFloatFlag = cli.FlagBase[float64, JSONConfig, JsonValueCreator[float64]]
type JSONDatetimeFlag = cli.FlagBase[time.Time, JSONConfig, JsonValueCreator[time.Time]]
type JSONDateFlag = cli.FlagBase[time.Time, JSONConfig, JsonDateValueCreator]
type JSONAnyFlag = cli.FlagBase[interface{}, JSONConfig, JsonValueCreator[interface{}]]

// JsonFileValueCreator handles file-based flags that read content and register with mutations
type JsonFileValueCreator struct{}

Expand Down Expand Up @@ -281,7 +273,7 @@ func (v *jsonFileValue) Set(filePath string) error {
*v.destination = filePath

// Register the file content with the global registry
globalRegistry.Register(v.config.Kind, v.config.Path, string(content))
globalRegistry.Mutate(v.config.Kind, v.config.Path, string(content))
return nil
}

Expand All @@ -304,3 +296,10 @@ func (v *jsonFileValue) IsBoolFlag() bool {
}

type JSONFileFlag = cli.FlagBase[string, JSONConfig, JsonFileValueCreator]
type JSONStringFlag = cli.FlagBase[string, JSONConfig, JSONValueCreator[string]]
type JSONBoolFlag = cli.FlagBase[bool, JSONConfig, JSONValueCreator[bool]]
type JSONIntFlag = cli.FlagBase[int, JSONConfig, JSONValueCreator[int]]
type JSONFloatFlag = cli.FlagBase[float64, JSONConfig, JSONValueCreator[float64]]
type JSONDatetimeFlag = cli.FlagBase[time.Time, JSONConfig, JSONValueCreator[time.Time]]
type JSONDateFlag = cli.FlagBase[time.Time, JSONConfig, JSONDateValueCreator]
type JSONAnyFlag = cli.FlagBase[interface{}, JSONConfig, JSONValueCreator[interface{}]]
24 changes: 15 additions & 9 deletions pkg/jsonflag/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ type registry struct {

var globalRegistry = &registry{}

func (r *registry) Register(kind MutationKind, path string, value interface{}) {
func (r *registry) Mutate(kind MutationKind, path string, value interface{}) {
r.mutations = append(r.mutations, Mutation{
Kind: kind,
Path: path,
Value: value,
})
}

func (r *registry) ApplyMutations(body, query, header []byte) ([]byte, []byte, []byte, error) {
func (r *registry) Apply(body, query, header []byte) ([]byte, []byte, []byte, error) {
var err error

for _, mutation := range r.mutations {
Expand Down Expand Up @@ -67,18 +67,24 @@ func (r *registry) List() []Mutation {
return result
}

func Apply(body, query, header []byte) ([]byte, []byte, []byte, error) {
body, query, header, err := globalRegistry.ApplyMutations(body, query, header)
globalRegistry.Clear()
return body, query, header, err
// Mutate adds a mutation that will be applied to the specified kind of data
func Mutate(kind MutationKind, path string, value interface{}) {
globalRegistry.Mutate(kind, path, value)
}

// ApplyMutations applies all registered mutations to the provided JSON data
func ApplyMutations(body, query, header []byte) ([]byte, []byte, []byte, error) {
return globalRegistry.Apply(body, query, header)
}

func Clear() {
// ClearMutations removes all registered mutations from the global registry
func ClearMutations() {
globalRegistry.Clear()
}

func Register(kind MutationKind, path string, value interface{}) {
globalRegistry.Register(kind, path, value)
// ListMutations returns a copy of all currently registered mutations
func ListMutations() []Mutation {
return globalRegistry.List()
}

func jsonSet(json []byte, path string, value interface{}) ([]byte, error) {
Expand Down
12 changes: 5 additions & 7 deletions pkg/jsonflag/mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ package jsonflag

import (
"testing"

"github.com/urfave/cli/v3"
)

func TestApply(t *testing.T) {
Clear()
ClearMutations()

globalRegistry.Register(Body, "name", "test")
globalRegistry.Register(Query, "page", 1)
globalRegistry.Register(Header, "authorization", "Bearer token")
Mutate(Body, "name", "test")
Mutate(Query, "page", 1)
Mutate(Header, "authorization", "Bearer token")

body, query, header, err := globalRegistry.ApplyMutations(
body, query, header, err := ApplyMutations(
[]byte(`{}`),
[]byte(`{}`),
[]byte(`{}`),
Expand Down
Loading