From 05f10bcf905e5fd4a15f559c9ab3e29aa9446f2a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 25 Jul 2025 10:49:05 +0200 Subject: [PATCH 1/3] fix(cosmosgen): skip non conventional repo struct --- ignite/pkg/cosmosgen/generate.go | 5 +++-- ignite/pkg/cosmosgen/generate_openapi.go | 2 +- ignite/pkg/cosmosgen/generate_typescript.go | 6 ++++++ ignite/services/chain/generate.go | 2 -- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ignite/pkg/cosmosgen/generate.go b/ignite/pkg/cosmosgen/generate.go index 4a75818ac6..43aa476fdb 100644 --- a/ignite/pkg/cosmosgen/generate.go +++ b/ignite/pkg/cosmosgen/generate.go @@ -234,7 +234,7 @@ func (g *generator) processThirdPartyModules(ctx context.Context) error { depInfo, err = g.processNewDependency(ctx, dep) if err == nil && len(depInfo.Modules) > 0 && depInfo.Cacheable { - // Cache the result only if it's safe to do so + // Cache the result only if it's safe to do _ = moduleCache.Put(cacheKey, depInfo) } } @@ -404,7 +404,8 @@ func (g *generator) resolveIncludes(ctx context.Context, path, protoDir string) } else { protoPath = filepath.Join(path, protoDir) if fi, err := os.Stat(protoPath); os.IsNotExist(err) { - return protoIncludes{}, false, errors.Errorf("proto directory %s does not exist", protoPath) + // if proto directory does not exist, we just skip it. + return includes, false, nil } else if err != nil { return protoIncludes{}, false, err } else if !fi.IsDir() { diff --git a/ignite/pkg/cosmosgen/generate_openapi.go b/ignite/pkg/cosmosgen/generate_openapi.go index 4a3beb6082..2d36bac774 100644 --- a/ignite/pkg/cosmosgen/generate_openapi.go +++ b/ignite/pkg/cosmosgen/generate_openapi.go @@ -93,7 +93,7 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error { ), cosmosbuf.FileByFile(), ); err != nil { - return errors.Wrapf(err, "failed to generate openapi spec %s, probally you need to exclude some proto files", protoPath) + return errors.Wrapf(err, "failed to generate openapi spec %s, probably you need to exclude some proto files", protoPath) } specs, err := xos.FindFiles(dir, xos.WithExtension(xos.JSONFile)) diff --git a/ignite/pkg/cosmosgen/generate_typescript.go b/ignite/pkg/cosmosgen/generate_typescript.go index 952bb083e0..0b496a5619 100644 --- a/ignite/pkg/cosmosgen/generate_typescript.go +++ b/ignite/pkg/cosmosgen/generate_typescript.go @@ -142,6 +142,12 @@ func (g *tsGenerator) generateModuleTemplate( protoPath = filepath.Join(g.g.sdkDir, "proto") } + // check if directory exists + if _, err := os.Stat(protoPath); os.IsNotExist(err) { + // if proto directory does not exist, we just skip it. + return nil + } + // code generate for each module. if err := g.g.buf.Generate( ctx, diff --git a/ignite/services/chain/generate.go b/ignite/services/chain/generate.go index 97c8f05cef..5b32c31c4e 100644 --- a/ignite/services/chain/generate.go +++ b/ignite/services/chain/generate.go @@ -41,7 +41,6 @@ func GenerateGo() GenerateTarget { // overriding the configured or default path. Path can be an empty string. func GenerateTSClient(path string, useCache bool) GenerateTarget { return func(o *generateOptions) { - o.isOpenAPIEnabled = true o.isTSClientEnabled = true o.tsClientPath = path o.useCache = useCache @@ -51,7 +50,6 @@ func GenerateTSClient(path string, useCache bool) GenerateTarget { // GenerateComposables enables generating proto based Typescript Client and Vue 3 composables. func GenerateComposables(path string) GenerateTarget { return func(o *generateOptions) { - o.isOpenAPIEnabled = true o.isTSClientEnabled = true o.isComposablesEnabled = true o.composablesPath = path From 97ea8a4d0bb99906ad2c8c8505ac34533d911135 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 25 Jul 2025 11:04:31 +0200 Subject: [PATCH 2/3] find them --- ignite/pkg/cosmosgen/generate.go | 53 ++++++++- ignite/pkg/cosmosgen/generate_test.go | 112 ++++++++++++++++++++ ignite/pkg/cosmosgen/generate_typescript.go | 10 +- 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 ignite/pkg/cosmosgen/generate_test.go diff --git a/ignite/pkg/cosmosgen/generate.go b/ignite/pkg/cosmosgen/generate.go index 43aa476fdb..7d07288bab 100644 --- a/ignite/pkg/cosmosgen/generate.go +++ b/ignite/pkg/cosmosgen/generate.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io/fs" + "log" "os" "path/filepath" "slices" @@ -404,8 +405,12 @@ func (g *generator) resolveIncludes(ctx context.Context, path, protoDir string) } else { protoPath = filepath.Join(path, protoDir) if fi, err := os.Stat(protoPath); os.IsNotExist(err) { - // if proto directory does not exist, we just skip it. - return includes, false, nil + protoPath, err = findInnerProtoFolder(path) + if err != nil { + // if proto directory does not exist, we just skip it + log.Print(err.Error()) + return protoIncludes{}, false, nil + } } else if err != nil { return protoIncludes{}, false, err } else if !fi.IsDir() { @@ -686,3 +691,47 @@ func filterCosmosSDKModule(versions []gomodule.Version) (gomodule.Version, bool) } return gomodule.Version{}, false } + +// findInnerProtoFolder attempts to find the proto directory in a module. +// it should be used as a fallback when the proto directory is not found in the expected location. +func findInnerProtoFolder(path string) (string, error) { + // attempt to find proto directory in the module + protoFiles, err := xos.FindFiles(path, xos.WithExtension(xos.ProtoFile)) + if err != nil { + return "", err + } + if len(protoFiles) == 0 { + return "", errors.Errorf("no proto folders found in %s", path) + } + + var protoDirs []string + for _, p := range protoFiles { + dir := filepath.Dir(p) + for { + if filepath.Base(dir) == "proto" { + protoDirs = append(protoDirs, dir) + break + } + parent := filepath.Dir(dir) + if parent == dir { // reached root + break + } + dir = parent + } + } + + if len(protoDirs) == 0 { + // Fallback to the parent of the first proto file found. + return filepath.Dir(protoFiles[0]), nil + } + + // Find the highest level proto directory (shortest path) + highest := protoDirs[0] + for _, d := range protoDirs[1:] { + if len(d) < len(highest) { + highest = d + } + } + + return highest, nil +} diff --git a/ignite/pkg/cosmosgen/generate_test.go b/ignite/pkg/cosmosgen/generate_test.go new file mode 100644 index 0000000000..931af0a79f --- /dev/null +++ b/ignite/pkg/cosmosgen/generate_test.go @@ -0,0 +1,112 @@ +package cosmosgen + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFindInnerProtoFolder(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "proto-test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // create dummy files + create := func(path string) { + dir := filepath.Dir(path) + err := os.MkdirAll(dir, 0o755) + require.NoError(t, err) + _, err = os.Create(path) + require.NoError(t, err) + } + + tests := []struct { + name string + setup func(root string) + expectedPath string + expectError bool + }{ + { + name: "no proto files", + setup: func(root string) { + // No files created + }, + expectError: true, + }, + { + name: "single proto file in root", + setup: func(root string) { + create(filepath.Join(root, "a.proto")) + }, + expectedPath: ".", + }, + { + name: "single proto file in proto dir", + setup: func(root string) { + create(filepath.Join(root, "proto", "a.proto")) + }, + expectedPath: "proto", + }, + { + name: "multiple proto files in same proto dir", + setup: func(root string) { + create(filepath.Join(root, "proto", "a.proto")) + create(filepath.Join(root, "proto", "b.proto")) + }, + expectedPath: "proto", + }, + { + name: "nested proto directories", + setup: func(root string) { + create(filepath.Join(root, "proto", "a.proto")) + create(filepath.Join(root, "proto", "api", "v1", "b.proto")) + }, + expectedPath: "proto", + }, + { + name: "highest level proto directory", + setup: func(root string) { + create(filepath.Join(root, "proto", "a.proto")) + create(filepath.Join(root, "foo", "proto", "b.proto")) + }, + expectedPath: "proto", + }, + { + name: "no dir named proto", + setup: func(root string) { + create(filepath.Join(root, "api", "a.proto")) + }, + expectedPath: "api", + }, + { + name: "deeply nested with no proto dir name", + setup: func(root string) { + create(filepath.Join(root, "foo", "bar", "a.proto")) + }, + expectedPath: "foo/bar", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caseRoot := filepath.Join(tmpDir, tt.name) + err := os.MkdirAll(caseRoot, 0o755) + require.NoError(t, err) + + tt.setup(caseRoot) + + result, err := findInnerProtoFolder(caseRoot) + + if tt.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + + expected := filepath.Join(caseRoot, tt.expectedPath) + require.Equal(t, expected, result) + }) + } +} diff --git a/ignite/pkg/cosmosgen/generate_typescript.go b/ignite/pkg/cosmosgen/generate_typescript.go index 0b496a5619..4dec5b2953 100644 --- a/ignite/pkg/cosmosgen/generate_typescript.go +++ b/ignite/pkg/cosmosgen/generate_typescript.go @@ -2,6 +2,7 @@ package cosmosgen import ( "context" + "log" "os" "path/filepath" "sort" @@ -144,8 +145,13 @@ func (g *tsGenerator) generateModuleTemplate( // check if directory exists if _, err := os.Stat(protoPath); os.IsNotExist(err) { - // if proto directory does not exist, we just skip it. - return nil + var err error + protoPath, err = findInnerProtoFolder(appPath) + if err != nil { + // if proto directory does not exist, we just skip it + log.Print(err.Error()) + return nil + } } // code generate for each module. From de29dfe5799a0c32b89deea7ec9e4a865c01f1a1 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 25 Jul 2025 11:22:07 +0200 Subject: [PATCH 3/3] add cl + fix openapi generation --- changelog.md | 10 +++++ ignite/pkg/cosmosgen/generate_openapi.go | 47 ++++++++++++++++++------ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index e2cc656038..22f3962a24 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,16 @@ ## Unreleased +## [`v29.2.1`](https://github.com/ignite/cli/releases/tag/v29.2.1) + +### Changes + +- [#4779](https://github.com/ignite/cli/pull/4779) Do not re-gen openapi spec each time the `ts-client` or the `composables` are generated. + +### Fixes + +- [#4779](https://github.com/ignite/cli/pull/4779) Find proto dir in non conventional repo structure. + ## [`v29.2.0`](https://github.com/ignite/cli/releases/tag/v29.2.0) ### Features diff --git a/ignite/pkg/cosmosgen/generate_openapi.go b/ignite/pkg/cosmosgen/generate_openapi.go index 2d36bac774..364e78578b 100644 --- a/ignite/pkg/cosmosgen/generate_openapi.go +++ b/ignite/pkg/cosmosgen/generate_openapi.go @@ -3,6 +3,7 @@ package cosmosgen import ( "context" "fmt" + "log" "os" "path/filepath" "strings" @@ -48,6 +49,17 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error { name = strcase.ToCamel(name) protoPath := filepath.Join(appPath, protoDir) + // check if directory exists + if _, err := os.Stat(protoPath); os.IsNotExist(err) { + var err error + protoPath, err = findInnerProtoFolder(appPath) + if err != nil { + // if proto directory does not exist, we just skip it + log.Print(err.Error()) + return nil + } + } + dir, err := os.MkdirTemp("", "gen-openapi-module-spec") if err != nil { return err @@ -55,22 +67,28 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error { specDirs = append(specDirs, dir) + var noChecksum bool checksum, err := dirchange.ChecksumFromPaths(appPath, protoDir) - if err != nil { - return err - } - cacheKey := fmt.Sprintf("%x", checksum) - existingSpec, err := specCache.Get(cacheKey) - if err != nil && !errors.Is(err, cache.ErrorNotFound) { + if errors.Is(err, dirchange.ErrNoFile) { + noChecksum = true + } else if err != nil { return err } - if !errors.Is(err, cache.ErrorNotFound) { - specPath := filepath.Join(dir, specFilename) - if err := os.WriteFile(specPath, existingSpec, 0o600); err != nil { + cacheKey := fmt.Sprintf("%x", checksum) + if !noChecksum { + existingSpec, err := specCache.Get(cacheKey) + if err != nil && !errors.Is(err, cache.ErrorNotFound) { return err } - return conf.AddSpec(name, specPath, true) + + if !errors.Is(err, cache.ErrorNotFound) { + specPath := filepath.Join(dir, specFilename) + if err := os.WriteFile(specPath, existingSpec, 0o600); err != nil { + return err + } + return conf.AddSpec(name, specPath, true) + } } hasAnySpecChanged = true @@ -106,9 +124,14 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error { if err != nil { return err } - if err := specCache.Put(cacheKey, f); err != nil { - return err + + // if no checksum, the cacheKey is wrong, so we do not save it + if !noChecksum { + if err := specCache.Put(cacheKey, f); err != nil { + return err + } } + if err := conf.AddSpec(name, spec, true); err != nil { return err }