mukan-ignite/ignite/pkg/cosmosgen/generate_openapi.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

209 lines
5 KiB
Go

package cosmosgen
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/blang/semver/v4"
"github.com/iancoleman/strcase"
"github.com/ignite/cli/v29/ignite/pkg/cache"
"github.com/ignite/cli/v29/ignite/pkg/cosmosbuf"
"github.com/ignite/cli/v29/ignite/pkg/dirchange"
"github.com/ignite/cli/v29/ignite/pkg/errors"
swaggercombine "github.com/ignite/cli/v29/ignite/pkg/swagger-combine"
"github.com/ignite/cli/v29/ignite/pkg/xos"
)
const (
specCacheNamespace = "generate.openapi.spec"
specFilename = "swagger.config.json"
)
func (g *generator) openAPITemplate() string {
return filepath.Join(g.appPath, g.protoDir, "buf.gen.swagger.yaml")
}
func (g *generator) generateOpenAPISpec(ctx context.Context, excludeList ...string) error {
var (
specDirs = make([]string, 0)
conf = swaggercombine.New("HTTP API Console", g.goModPath)
)
defer func() {
for _, dir := range specDirs {
_ = os.RemoveAll(dir)
}
}()
specCache := cache.New[[]byte](g.cacheStorage, specCacheNamespace)
var hasAnySpecChanged bool
// gen generates a spec for a module where it's source code resides at src.
// and adds needed swaggercombine configure for it.
gen := func(appPath, protoDir, name string) 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 errors.Is(err, dirchange.ErrNoFile) {
noChecksum = true
} else if err != nil {
return err
}
cacheKey := fmt.Sprintf("%x", checksum)
if !noChecksum {
existingSpec, err := specCache.Get(cacheKey)
if err != nil && !errors.Is(err, cache.ErrorNotFound) {
return err
}
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
if err = g.buf.Generate(
ctx,
protoPath,
dir,
g.openAPITemplate(),
cosmosbuf.ExcludeFiles(
append(excludeList, []string{
"*/strangelove_ventures/poa/*",
"*/osmosis-labs/fee-abstraction/*",
"*/module.proto",
"*/testutil/*",
"*/testdata/*",
"*/cosmos/orm/*",
"*/cosmos/reflection/*",
"*/cosmos/app/v1alpha1/*",
"*/cosmos/tx/config/v1/config.proto",
"*/cosmos/msg/textual/v1/textual.proto",
"*/cosmos/vesting/v1beta1/vesting.proto",
}...)...,
),
cosmosbuf.FileByFile(),
); err != nil {
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))
if err != nil {
return err
}
for _, spec := range specs {
f, err := os.ReadFile(spec)
if 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
}
}
return nil
}
// generate specs for each module and persist them in the file system
// after add their path and config to swaggercombine.Config so we can combine them
// into a single spec.
// protoc openapi generator acts weird on concurrent run, so do not use goroutines here.
if err := gen(g.appPath, g.protoDir, g.goModPath); err != nil {
return err
}
doneMods := make(map[string]struct{})
for _, modules := range g.thirdModules {
for _, m := range modules {
path := extractRootModulePath(m.Pkg.Path)
if _, ok := doneMods[path]; ok {
continue
}
doneMods[path] = struct{}{}
if err := gen(path, "proto", m.Name); err != nil {
return err
}
}
}
out := g.opts.openAPISpecOut
if !hasAnySpecChanged {
// In case the generated output has been changed
changed, err := dirchange.HasDirChecksumChanged(specCache, out, g.appPath, out)
if err != nil {
return err
}
if !changed {
return nil
}
}
// combine specs into one and save to out.
if err := conf.Combine(out); err != nil {
return err
}
return dirchange.SaveDirChecksum(specCache, out, g.appPath, out)
}
func extractRootModulePath(fullPath string) string {
var (
segments = strings.Split(fullPath, "/")
modulePath = "/"
)
for _, segment := range segments {
modulePath = filepath.Join(modulePath, segment)
segmentName := strings.Split(segment, "@")
if len(segmentName) > 1 {
if _, err := semver.ParseTolerant(segmentName[1]); err == nil {
return modulePath
}
}
}
return fullPath
}