diff --git a/changelog.md b/changelog.md index bab189ffec..a66b2d4a4a 100644 --- a/changelog.md +++ b/changelog.md @@ -2,9 +2,10 @@ ## Unreleased -### Features +### Changes - [#4807](https://github.com/ignite/cli/pull/4807) Improve unconfigured path message when building a chain. +- [#4805](https://github.com/ignite/cli/pull/4805) Fetch fallback buf token. ## [`v29.4.0`](https://github.com/ignite/cli/releases/tag/v29.4.0) diff --git a/ignite/cmd/bubblemodel/chain_serve.go b/ignite/cmd/bubblemodel/chain_serve.go index 08cfa298f5..088c81c091 100644 --- a/ignite/cmd/bubblemodel/chain_serve.go +++ b/ignite/cmd/bubblemodel/chain_serve.go @@ -7,7 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" - "github.com/ignite/cli/v29/ignite/pkg/announcements" + "github.com/ignite/cli/v29/ignite/internal/announcements" "github.com/ignite/cli/v29/ignite/pkg/cliui/colors" "github.com/ignite/cli/v29/ignite/pkg/cliui/icons" cliuimodel "github.com/ignite/cli/v29/ignite/pkg/cliui/model" diff --git a/ignite/cmd/cmd.go b/ignite/cmd/cmd.go index e5b937be35..19979a3cfd 100644 --- a/ignite/cmd/cmd.go +++ b/ignite/cmd/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ignite/cli/v29/ignite/config" chainconfig "github.com/ignite/cli/v29/ignite/config/chain" - "github.com/ignite/cli/v29/ignite/pkg/announcements" + "github.com/ignite/cli/v29/ignite/internal/announcements" "github.com/ignite/cli/v29/ignite/pkg/cache" "github.com/ignite/cli/v29/ignite/pkg/cliui" uilog "github.com/ignite/cli/v29/ignite/pkg/cliui/log" diff --git a/ignite/internal/analytics/analytics.go b/ignite/internal/analytics/analytics.go index 5c55290d37..5a83512003 100644 --- a/ignite/internal/analytics/analytics.go +++ b/ignite/internal/analytics/analytics.go @@ -12,11 +12,11 @@ import ( "github.com/spf13/cobra" "github.com/ignite/cli/v29/ignite/config" + "github.com/ignite/cli/v29/ignite/internal/sentry" "github.com/ignite/cli/v29/ignite/pkg/cliui" "github.com/ignite/cli/v29/ignite/pkg/errors" "github.com/ignite/cli/v29/ignite/pkg/matomo" "github.com/ignite/cli/v29/ignite/pkg/randstr" - "github.com/ignite/cli/v29/ignite/pkg/sentry" "github.com/ignite/cli/v29/ignite/version" ) diff --git a/ignite/pkg/announcements/announcement.go b/ignite/internal/announcements/announcement.go similarity index 78% rename from ignite/pkg/announcements/announcement.go rename to ignite/internal/announcements/announcement.go index 7b1947f54a..0c3c5058ad 100644 --- a/ignite/pkg/announcements/announcement.go +++ b/ignite/internal/announcements/announcement.go @@ -11,14 +11,10 @@ import ( ) var ( - SurveyLink = "https://bit.ly/3WZS2uS" - APIURL = "http://announcements.ignite.com/v1/announcements" + SurveyLink = "https://bit.ly/3WZS2uS" + AnnouncementURL = "https://api.ignite.com/v1/announcements" ) -type api struct { - Announcements []announcement `json:"announcements"` -} - type announcement struct { ID string `json:"id"` Text string `json:"text"` @@ -28,13 +24,16 @@ type announcement struct { // Fetch fetches the latest announcements from the API. func Fetch() string { - resp, err := http.Get(APIURL) //nolint:gosec + resp, err := http.Get(AnnouncementURL) //nolint:gosec if err != nil || resp.StatusCode != 200 { return fallbackData() } defer resp.Body.Close() - var data api + type response struct { + Announcements []announcement `json:"announcements"` + } + var data response if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return fallbackData() } diff --git a/ignite/pkg/announcements/announcement_test.go b/ignite/internal/announcements/announcement_test.go similarity index 87% rename from ignite/pkg/announcements/announcement_test.go rename to ignite/internal/announcements/announcement_test.go index eb1796c881..23a8e5b3c9 100644 --- a/ignite/pkg/announcements/announcement_test.go +++ b/ignite/internal/announcements/announcement_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/ignite/cli/v29/ignite/pkg/announcements" + "github.com/ignite/cli/v29/ignite/internal/announcements" ) func TestFetchAnnouncements(t *testing.T) { @@ -52,9 +52,9 @@ func TestFetchAnnouncements(t *testing.T) { })) defer server.Close() - originalAPI := announcements.APIURL - announcements.APIURL = server.URL - defer func() { announcements.APIURL = originalAPI }() + originalAPI := announcements.AnnouncementURL + announcements.AnnouncementURL = server.URL + defer func() { announcements.AnnouncementURL = originalAPI }() result := announcements.Fetch() if result != tt.expected { diff --git a/ignite/internal/buf/buf.go b/ignite/internal/buf/buf.go new file mode 100644 index 0000000000..268ee2af27 --- /dev/null +++ b/ignite/internal/buf/buf.go @@ -0,0 +1,39 @@ +package buf + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/ignite/cli/v29/ignite/pkg/errors" +) + +var BufTokenURL = "https://buf.ignite.com" //nolint:gosec // URL is hardcoded and not user-provided + +// FetchToken fetches the buf token from the Ignite API. +func FetchToken() (string, error) { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + resp, err := client.Get(BufTokenURL) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", errors.Errorf("HTTP request failed with status code: %d", resp.StatusCode) + } + + type tokenResponse struct { + Token string `json:"token"` + } + var tokenResp tokenResponse + + if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { + return "", err + } + + return tokenResp.Token, nil +} diff --git a/ignite/internal/buf/buf_test.go b/ignite/internal/buf/buf_test.go new file mode 100644 index 0000000000..a2f167cd6e --- /dev/null +++ b/ignite/internal/buf/buf_test.go @@ -0,0 +1,69 @@ +package buf_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/ignite/cli/v29/ignite/internal/buf" + "github.com/stretchr/testify/require" +) + +func TestFetchToken(t *testing.T) { + tests := []struct { + name string + serverResponse string + statusCode int + expectedToken string + expectError bool + }{ + { + name: "successful token fetch", + serverResponse: `{"token":"test_token_123"}`, + statusCode: http.StatusOK, + expectedToken: "test_token_123", + expectError: false, + }, + { + name: "server error", + serverResponse: `{"error":"internal server error"}`, + statusCode: http.StatusInternalServerError, + expectedToken: "", + expectError: true, + }, + { + name: "invalid json response", + serverResponse: `invalid json`, + statusCode: http.StatusOK, + expectedToken: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tt.statusCode) + w.Write([]byte(tt.serverResponse)) + })) + defer server.Close() + + // Temporarily override the endpoint + originalEndpoint := buf.BufTokenURL + buf.BufTokenURL = server.URL + defer func() { + buf.BufTokenURL = originalEndpoint + }() + + token, err := buf.FetchToken() + if tt.expectError { + require.Error(t, err) + require.Empty(t, token) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedToken, token) + } + }) + } +} diff --git a/ignite/pkg/sentry/sentry.go b/ignite/internal/sentry/sentry.go similarity index 100% rename from ignite/pkg/sentry/sentry.go rename to ignite/internal/sentry/sentry.go diff --git a/ignite/pkg/cosmosgen/generate_typescript.go b/ignite/pkg/cosmosgen/generate_typescript.go index c6919d805a..10e8c5a440 100644 --- a/ignite/pkg/cosmosgen/generate_typescript.go +++ b/ignite/pkg/cosmosgen/generate_typescript.go @@ -11,6 +11,7 @@ import ( "golang.org/x/sync/errgroup" + "github.com/ignite/cli/v29/ignite/internal/buf" "github.com/ignite/cli/v29/ignite/pkg/cache" "github.com/ignite/cli/v29/ignite/pkg/cosmosanalysis/module" "github.com/ignite/cli/v29/ignite/pkg/cosmosbuf" @@ -19,6 +20,8 @@ import ( ) var ( + bufTokenEnvName = "BUF_TOKEN" + dirchangeCacheNamespace = "generate.typescript.dirchange" protocGenTSProtoBin = "protoc-gen-ts_proto" @@ -31,15 +34,21 @@ plugins: - plugin: ts_proto out: . opt: - - "esModuleInterop=true" - - "forceLong=long" - - "useOptionals=true" + - logtostderr=true + - allow_merge=true + - json_names_for_fields=false + - ts_proto_opt=snakeToCamel=true + - ts_proto_opt=esModuleInterop=true + - ts_proto_out=. ` type tsGenerator struct { g *generator tsTemplateFile string isLocalProto bool + + // hasLocalBufToken indicates whether the user had already a local Buf token. + hasLocalBufToken bool } type generatePayload struct { @@ -54,7 +63,16 @@ func newTSGenerator(g *generator) *tsGenerator { } if !tsg.isLocalProto { - log.Printf("No '%s' binary found in PATH, using remote buf plugin for Typescript generation. %s\n", protocGenTSProtoBin, msgBufAuth) + if os.Getenv(bufTokenEnvName) == "" { + token, err := buf.FetchToken() + if err != nil { + log.Printf("No '%s' binary found in PATH, using remote buf plugin for Typescript generation. %s\n", protocGenTSProtoBin, msgBufAuth) + } else { + os.Setenv(bufTokenEnvName, token) + } + } else { + tsg.hasLocalBufToken = true + } } return tsg @@ -83,6 +101,11 @@ func (g *tsGenerator) cleanup() { if g.tsTemplateFile != "" { os.Remove(g.tsTemplateFile) } + + // unset ignite buf token from env + if !g.hasLocalBufToken { + os.Unsetenv(bufTokenEnvName) + } } func (g *generator) tsTemplate() string { diff --git a/ignite/pkg/cosmosgen/testdata/testchain/proto/buf.gen.swagger.yaml b/ignite/pkg/cosmosgen/testdata/testchain/proto/buf.gen.swagger.yaml index 0061ef290e..d81685c33d 100644 --- a/ignite/pkg/cosmosgen/testdata/testchain/proto/buf.gen.swagger.yaml +++ b/ignite/pkg/cosmosgen/testdata/testchain/proto/buf.gen.swagger.yaml @@ -1,4 +1,4 @@ -# This file is auto-generated from Ignite. You can edit +# This file is auto-generated by Ignite. You can edit # the file content but do not change the file name or path. # # buf.gen.swagger.yaml diff --git a/ignite/pkg/errors/xerrors.go b/ignite/pkg/errors/xerrors.go index 66ba2c074e..8261f898e6 100644 --- a/ignite/pkg/errors/xerrors.go +++ b/ignite/pkg/errors/xerrors.go @@ -26,7 +26,7 @@ func New(msg string) error { } // Errorf aliases Newf(). -func Errorf(format string, args ...interface{}) error { +func Errorf(format string, args ...any) error { err := errors.Errorf(format, args...) sentry.CaptureException(err) return err @@ -42,16 +42,20 @@ func WithStack(err error) error { // Wrap wraps an error with a message prefix. A stack trace is retained. func Wrap(err error, msg string) error { errWrap := errors.Wrap(err, msg) - sentry.CaptureException(errWrap) + if err != nil { + sentry.CaptureException(errWrap) + } return errWrap } // Wrapf wraps an error with a formatted message prefix. A stack // trace is also retained. If the format is empty, no prefix is added, // but the extra arguments are still processed for reportable strings. -func Wrapf(err error, format string, args ...interface{}) error { +func Wrapf(err error, format string, args ...any) error { errWrap := errors.Wrapf(err, format, args...) - sentry.CaptureException(errWrap) + if err != nil { + sentry.CaptureException(errWrap) + } return errWrap } @@ -73,4 +77,4 @@ func Is(err, reference error) bool { return errors.Is(err, reference) } // matches a type if it is assignable to the target type, or if it has a method // As(interface{}) bool such that As(target) returns true. As will panic if target // is not a non-nil pointer to a type which implements error or is of interface type. -func As(err error, target interface{}) bool { return errors.As(err, target) } +func As(err error, target any) bool { return errors.As(err, target) } diff --git a/ignite/pkg/gocmd/gocmd.go b/ignite/pkg/gocmd/gocmd.go index c5216df876..db3aedb1e1 100644 --- a/ignite/pkg/gocmd/gocmd.go +++ b/ignite/pkg/gocmd/gocmd.go @@ -168,14 +168,6 @@ func Install(ctx context.Context, path string, pkgs []string, options ...exec.Op return exec.Exec(ctx, command, append(options, exec.StepOption(step.Workdir(path)))...) } -// IsInstallError returns true if err is interpreted as a go install error. -func IsInstallError(err error) bool { - if err == nil { - return false - } - return strings.Contains(err.Error(), "no required module provides package") -} - // Get runs go get pkgs on path with options. func Get(ctx context.Context, path string, pkgs []string, options ...exec.Option) error { command := []string{ diff --git a/ignite/pkg/gocmd/gocmd_test.go b/ignite/pkg/gocmd/gocmd_test.go index fc33beb654..bf3a3e69dd 100644 --- a/ignite/pkg/gocmd/gocmd_test.go +++ b/ignite/pkg/gocmd/gocmd_test.go @@ -7,18 +7,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ignite/cli/v29/ignite/pkg/errors" "github.com/ignite/cli/v29/ignite/pkg/gocmd" ) -func TestIsInstallError(t *testing.T) { - assert.False(t, gocmd.IsInstallError(errors.New("oups"))) - - err := errors.New(`error while running command go install github.com/cosmos/gogoproto/protoc-gen-gocosmos google.golang.org/protobuf/cmd/protoc-gen-go github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2: no required module provides package github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2; to add it: - go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2`) - assert.True(t, gocmd.IsInstallError(err)) -} - func TestList(t *testing.T) { wd, err := os.Getwd() assert.NoError(t, err) diff --git a/ignite/templates/app/files/{{protoDir}}/buf.gen.gogo.yaml b/ignite/templates/app/files/{{protoDir}}/buf.gen.gogo.yaml index 1f6ecfe5e0..a3b72cc944 100644 --- a/ignite/templates/app/files/{{protoDir}}/buf.gen.gogo.yaml +++ b/ignite/templates/app/files/{{protoDir}}/buf.gen.gogo.yaml @@ -1,4 +1,4 @@ -# This file is auto-generated from Ignite. You can edit +# This file is auto-generated by Ignite. You can edit # the file content but do not change the file name or path. # # buf.gen.gogo.yaml diff --git a/ignite/templates/app/files/{{protoDir}}/buf.gen.sta.yaml b/ignite/templates/app/files/{{protoDir}}/buf.gen.sta.yaml index 215d950d94..7e69ed5d2e 100644 --- a/ignite/templates/app/files/{{protoDir}}/buf.gen.sta.yaml +++ b/ignite/templates/app/files/{{protoDir}}/buf.gen.sta.yaml @@ -1,4 +1,4 @@ -# This file is auto-generated from Ignite. You can edit +# This file is auto-generated by Ignite. You can edit # the file content but do not change the file name or path. # # buf.gen.sta.yaml diff --git a/ignite/templates/app/files/{{protoDir}}/buf.gen.swagger.yaml b/ignite/templates/app/files/{{protoDir}}/buf.gen.swagger.yaml index 0061ef290e..d81685c33d 100644 --- a/ignite/templates/app/files/{{protoDir}}/buf.gen.swagger.yaml +++ b/ignite/templates/app/files/{{protoDir}}/buf.gen.swagger.yaml @@ -1,4 +1,4 @@ -# This file is auto-generated from Ignite. You can edit +# This file is auto-generated by Ignite. You can edit # the file content but do not change the file name or path. # # buf.gen.swagger.yaml diff --git a/ignite/templates/app/files/{{protoDir}}/buf.gen.ts.yaml b/ignite/templates/app/files/{{protoDir}}/buf.gen.ts.yaml index bc2f01b93c..9bd0f601e7 100644 --- a/ignite/templates/app/files/{{protoDir}}/buf.gen.ts.yaml +++ b/ignite/templates/app/files/{{protoDir}}/buf.gen.ts.yaml @@ -1,4 +1,4 @@ -# This file is auto-generated from Ignite. You can edit +# This file is auto-generated by Ignite. You can edit # the file content but do not change the file name or path. # # buf.gen.ts.yaml