diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3cf71e6..b386bef 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.18" + ".": "0.1.0-alpha.19" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 7bf07f7..f3dbd23 100644 --- a/.stats.yml +++ b/.stats.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index ae166b5..b5e5064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/go.mod b/go.mod index c468549..81425e0 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index a3b3f7a..82534dd 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/cmd/build.go b/pkg/cmd/build.go index 25ff92d..fed7706 100644 --- a/pkg/cmd/build.go +++ b/pkg/cmd/build.go @@ -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 != "" { @@ -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 diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 818e942..6d5c1eb 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -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{ { diff --git a/pkg/cmd/project.go b/pkg/cmd/project.go index 17938af..43ec011 100644 --- a/pkg/cmd/project.go +++ b/pkg/cmd/project.go @@ -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), @@ -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 { @@ -294,7 +296,7 @@ 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)) } } @@ -302,12 +304,10 @@ func handleProjectsCreate(ctx context.Context, cmd *cli.Command) error { 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(), @@ -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 diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go index 63224d0..a305694 100644 --- a/pkg/cmd/util.go +++ b/pkg/cmd/util.go @@ -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 { @@ -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) } @@ -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} } diff --git a/pkg/jsonflag/json_flag.go b/pkg/jsonflag/json_flag.go index b5fdbce..4314124 100644 --- a/pkg/jsonflag/json_flag.go +++ b/pkg/jsonflag/json_flag.go @@ -16,9 +16,9 @@ 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, @@ -26,7 +26,7 @@ func (c JsonValueCreator[T]) Create(val T, dest *T, config JSONConfig) cli.Value } } -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 == "" { @@ -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 } @@ -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 } @@ -173,10 +173,10 @@ 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, @@ -184,7 +184,7 @@ func (c JsonDateValueCreator) Create(val time.Time, dest *time.Time, config JSON } } -func (c JsonDateValueCreator) ToString(val time.Time) string { +func (c JSONDateValueCreator) ToString(val time.Time) string { return val.Format("2006-01-02") } @@ -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 } @@ -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{} @@ -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 } @@ -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{}]] diff --git a/pkg/jsonflag/mutation.go b/pkg/jsonflag/mutation.go index b5c525c..d0eb78e 100644 --- a/pkg/jsonflag/mutation.go +++ b/pkg/jsonflag/mutation.go @@ -29,7 +29,7 @@ type registry struct { var globalRegistry = ®istry{} -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, @@ -37,7 +37,7 @@ func (r *registry) Register(kind MutationKind, path string, value interface{}) { }) } -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 { @@ -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) { diff --git a/pkg/jsonflag/mutation_test.go b/pkg/jsonflag/mutation_test.go index 1ba78e1..e87e518 100644 --- a/pkg/jsonflag/mutation_test.go +++ b/pkg/jsonflag/mutation_test.go @@ -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(`{}`),