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
10 changes: 10 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 52 additions & 2 deletions ignite/pkg/cosmosgen/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"slices"
Expand Down Expand Up @@ -234,7 +235,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)
}
}
Expand Down Expand Up @@ -404,7 +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) {
return protoIncludes{}, false, errors.Errorf("proto directory %s does not exist", protoPath)
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() {
Expand Down Expand Up @@ -685,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
}
49 changes: 36 additions & 13 deletions ignite/pkg/cosmosgen/generate_openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cosmosgen
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -48,29 +49,46 @@ 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
}

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
Expand All @@ -93,7 +111,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))
Expand All @@ -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
}
Expand Down
112 changes: 112 additions & 0 deletions ignite/pkg/cosmosgen/generate_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
12 changes: 12 additions & 0 deletions ignite/pkg/cosmosgen/generate_typescript.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cosmosgen

import (
"context"
"log"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -142,6 +143,17 @@ func (g *tsGenerator) generateModuleTemplate(
protoPath = filepath.Join(g.g.sdkDir, "proto")
}

// 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
}
}

// code generate for each module.
if err := g.g.buf.Generate(
ctx,
Expand Down
2 changes: 0 additions & 2 deletions ignite/services/chain/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading