Skip to content

Commit c3b7fd2

Browse files
authored
fix(cosmosgen): find proto dir in non conventional repo structure (#4779)
* fix(cosmosgen): skip non conventional repo struct * find them * add cl + fix openapi generation
1 parent 6f2de1e commit c3b7fd2

File tree

6 files changed

+222
-17
lines changed

6 files changed

+222
-17
lines changed

changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## Unreleased
44

5+
## [`v29.2.1`](https://github.com/ignite/cli/releases/tag/v29.2.1)
6+
7+
### Changes
8+
9+
- [#4779](https://github.com/ignite/cli/pull/4779) Do not re-gen openapi spec each time the `ts-client` or the `composables` are generated.
10+
11+
### Fixes
12+
13+
- [#4779](https://github.com/ignite/cli/pull/4779) Find proto dir in non conventional repo structure.
14+
515
## [`v29.2.0`](https://github.com/ignite/cli/releases/tag/v29.2.0)
616

717
### Features

ignite/pkg/cosmosgen/generate.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io/fs"
8+
"log"
89
"os"
910
"path/filepath"
1011
"slices"
@@ -234,7 +235,7 @@ func (g *generator) processThirdPartyModules(ctx context.Context) error {
234235

235236
depInfo, err = g.processNewDependency(ctx, dep)
236237
if err == nil && len(depInfo.Modules) > 0 && depInfo.Cacheable {
237-
// Cache the result only if it's safe to do so
238+
// Cache the result only if it's safe to do
238239
_ = moduleCache.Put(cacheKey, depInfo)
239240
}
240241
}
@@ -404,7 +405,12 @@ func (g *generator) resolveIncludes(ctx context.Context, path, protoDir string)
404405
} else {
405406
protoPath = filepath.Join(path, protoDir)
406407
if fi, err := os.Stat(protoPath); os.IsNotExist(err) {
407-
return protoIncludes{}, false, errors.Errorf("proto directory %s does not exist", protoPath)
408+
protoPath, err = findInnerProtoFolder(path)
409+
if err != nil {
410+
// if proto directory does not exist, we just skip it
411+
log.Print(err.Error())
412+
return protoIncludes{}, false, nil
413+
}
408414
} else if err != nil {
409415
return protoIncludes{}, false, err
410416
} else if !fi.IsDir() {
@@ -685,3 +691,47 @@ func filterCosmosSDKModule(versions []gomodule.Version) (gomodule.Version, bool)
685691
}
686692
return gomodule.Version{}, false
687693
}
694+
695+
// findInnerProtoFolder attempts to find the proto directory in a module.
696+
// it should be used as a fallback when the proto directory is not found in the expected location.
697+
func findInnerProtoFolder(path string) (string, error) {
698+
// attempt to find proto directory in the module
699+
protoFiles, err := xos.FindFiles(path, xos.WithExtension(xos.ProtoFile))
700+
if err != nil {
701+
return "", err
702+
}
703+
if len(protoFiles) == 0 {
704+
return "", errors.Errorf("no proto folders found in %s", path)
705+
}
706+
707+
var protoDirs []string
708+
for _, p := range protoFiles {
709+
dir := filepath.Dir(p)
710+
for {
711+
if filepath.Base(dir) == "proto" {
712+
protoDirs = append(protoDirs, dir)
713+
break
714+
}
715+
parent := filepath.Dir(dir)
716+
if parent == dir { // reached root
717+
break
718+
}
719+
dir = parent
720+
}
721+
}
722+
723+
if len(protoDirs) == 0 {
724+
// Fallback to the parent of the first proto file found.
725+
return filepath.Dir(protoFiles[0]), nil
726+
}
727+
728+
// Find the highest level proto directory (shortest path)
729+
highest := protoDirs[0]
730+
for _, d := range protoDirs[1:] {
731+
if len(d) < len(highest) {
732+
highest = d
733+
}
734+
}
735+
736+
return highest, nil
737+
}

ignite/pkg/cosmosgen/generate_openapi.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cosmosgen
33
import (
44
"context"
55
"fmt"
6+
"log"
67
"os"
78
"path/filepath"
89
"strings"
@@ -48,29 +49,46 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error {
4849
name = strcase.ToCamel(name)
4950
protoPath := filepath.Join(appPath, protoDir)
5051

52+
// check if directory exists
53+
if _, err := os.Stat(protoPath); os.IsNotExist(err) {
54+
var err error
55+
protoPath, err = findInnerProtoFolder(appPath)
56+
if err != nil {
57+
// if proto directory does not exist, we just skip it
58+
log.Print(err.Error())
59+
return nil
60+
}
61+
}
62+
5163
dir, err := os.MkdirTemp("", "gen-openapi-module-spec")
5264
if err != nil {
5365
return err
5466
}
5567

5668
specDirs = append(specDirs, dir)
5769

70+
var noChecksum bool
5871
checksum, err := dirchange.ChecksumFromPaths(appPath, protoDir)
59-
if err != nil {
60-
return err
61-
}
62-
cacheKey := fmt.Sprintf("%x", checksum)
63-
existingSpec, err := specCache.Get(cacheKey)
64-
if err != nil && !errors.Is(err, cache.ErrorNotFound) {
72+
if errors.Is(err, dirchange.ErrNoFile) {
73+
noChecksum = true
74+
} else if err != nil {
6575
return err
6676
}
6777

68-
if !errors.Is(err, cache.ErrorNotFound) {
69-
specPath := filepath.Join(dir, specFilename)
70-
if err := os.WriteFile(specPath, existingSpec, 0o600); err != nil {
78+
cacheKey := fmt.Sprintf("%x", checksum)
79+
if !noChecksum {
80+
existingSpec, err := specCache.Get(cacheKey)
81+
if err != nil && !errors.Is(err, cache.ErrorNotFound) {
7182
return err
7283
}
73-
return conf.AddSpec(name, specPath, true)
84+
85+
if !errors.Is(err, cache.ErrorNotFound) {
86+
specPath := filepath.Join(dir, specFilename)
87+
if err := os.WriteFile(specPath, existingSpec, 0o600); err != nil {
88+
return err
89+
}
90+
return conf.AddSpec(name, specPath, true)
91+
}
7492
}
7593

7694
hasAnySpecChanged = true
@@ -93,7 +111,7 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error {
93111
),
94112
cosmosbuf.FileByFile(),
95113
); err != nil {
96-
return errors.Wrapf(err, "failed to generate openapi spec %s, probally you need to exclude some proto files", protoPath)
114+
return errors.Wrapf(err, "failed to generate openapi spec %s, probably you need to exclude some proto files", protoPath)
97115
}
98116

99117
specs, err := xos.FindFiles(dir, xos.WithExtension(xos.JSONFile))
@@ -106,9 +124,14 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error {
106124
if err != nil {
107125
return err
108126
}
109-
if err := specCache.Put(cacheKey, f); err != nil {
110-
return err
127+
128+
// if no checksum, the cacheKey is wrong, so we do not save it
129+
if !noChecksum {
130+
if err := specCache.Put(cacheKey, f); err != nil {
131+
return err
132+
}
111133
}
134+
112135
if err := conf.AddSpec(name, spec, true); err != nil {
113136
return err
114137
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package cosmosgen
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestFindInnerProtoFolder(t *testing.T) {
12+
tmpDir, err := os.MkdirTemp("", "proto-test")
13+
require.NoError(t, err)
14+
defer os.RemoveAll(tmpDir)
15+
16+
// create dummy files
17+
create := func(path string) {
18+
dir := filepath.Dir(path)
19+
err := os.MkdirAll(dir, 0o755)
20+
require.NoError(t, err)
21+
_, err = os.Create(path)
22+
require.NoError(t, err)
23+
}
24+
25+
tests := []struct {
26+
name string
27+
setup func(root string)
28+
expectedPath string
29+
expectError bool
30+
}{
31+
{
32+
name: "no proto files",
33+
setup: func(root string) {
34+
// No files created
35+
},
36+
expectError: true,
37+
},
38+
{
39+
name: "single proto file in root",
40+
setup: func(root string) {
41+
create(filepath.Join(root, "a.proto"))
42+
},
43+
expectedPath: ".",
44+
},
45+
{
46+
name: "single proto file in proto dir",
47+
setup: func(root string) {
48+
create(filepath.Join(root, "proto", "a.proto"))
49+
},
50+
expectedPath: "proto",
51+
},
52+
{
53+
name: "multiple proto files in same proto dir",
54+
setup: func(root string) {
55+
create(filepath.Join(root, "proto", "a.proto"))
56+
create(filepath.Join(root, "proto", "b.proto"))
57+
},
58+
expectedPath: "proto",
59+
},
60+
{
61+
name: "nested proto directories",
62+
setup: func(root string) {
63+
create(filepath.Join(root, "proto", "a.proto"))
64+
create(filepath.Join(root, "proto", "api", "v1", "b.proto"))
65+
},
66+
expectedPath: "proto",
67+
},
68+
{
69+
name: "highest level proto directory",
70+
setup: func(root string) {
71+
create(filepath.Join(root, "proto", "a.proto"))
72+
create(filepath.Join(root, "foo", "proto", "b.proto"))
73+
},
74+
expectedPath: "proto",
75+
},
76+
{
77+
name: "no dir named proto",
78+
setup: func(root string) {
79+
create(filepath.Join(root, "api", "a.proto"))
80+
},
81+
expectedPath: "api",
82+
},
83+
{
84+
name: "deeply nested with no proto dir name",
85+
setup: func(root string) {
86+
create(filepath.Join(root, "foo", "bar", "a.proto"))
87+
},
88+
expectedPath: "foo/bar",
89+
},
90+
}
91+
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
caseRoot := filepath.Join(tmpDir, tt.name)
95+
err := os.MkdirAll(caseRoot, 0o755)
96+
require.NoError(t, err)
97+
98+
tt.setup(caseRoot)
99+
100+
result, err := findInnerProtoFolder(caseRoot)
101+
102+
if tt.expectError {
103+
require.Error(t, err)
104+
return
105+
}
106+
require.NoError(t, err)
107+
108+
expected := filepath.Join(caseRoot, tt.expectedPath)
109+
require.Equal(t, expected, result)
110+
})
111+
}
112+
}

ignite/pkg/cosmosgen/generate_typescript.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cosmosgen
22

33
import (
44
"context"
5+
"log"
56
"os"
67
"path/filepath"
78
"sort"
@@ -142,6 +143,17 @@ func (g *tsGenerator) generateModuleTemplate(
142143
protoPath = filepath.Join(g.g.sdkDir, "proto")
143144
}
144145

146+
// check if directory exists
147+
if _, err := os.Stat(protoPath); os.IsNotExist(err) {
148+
var err error
149+
protoPath, err = findInnerProtoFolder(appPath)
150+
if err != nil {
151+
// if proto directory does not exist, we just skip it
152+
log.Print(err.Error())
153+
return nil
154+
}
155+
}
156+
145157
// code generate for each module.
146158
if err := g.g.buf.Generate(
147159
ctx,

ignite/services/chain/generate.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ func GenerateGo() GenerateTarget {
4141
// overriding the configured or default path. Path can be an empty string.
4242
func GenerateTSClient(path string, useCache bool) GenerateTarget {
4343
return func(o *generateOptions) {
44-
o.isOpenAPIEnabled = true
4544
o.isTSClientEnabled = true
4645
o.tsClientPath = path
4746
o.useCache = useCache
@@ -51,7 +50,6 @@ func GenerateTSClient(path string, useCache bool) GenerateTarget {
5150
// GenerateComposables enables generating proto based Typescript Client and Vue 3 composables.
5251
func GenerateComposables(path string) GenerateTarget {
5352
return func(o *generateOptions) {
54-
o.isOpenAPIEnabled = true
5553
o.isTSClientEnabled = true
5654
o.isComposablesEnabled = true
5755
o.composablesPath = path

0 commit comments

Comments
 (0)