mukan-ignite/ignite/services/chain/generate.go
Mukan Erkin Törük c32551b6f7
Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:24 +03:00

278 lines
7.5 KiB
Go

package chain
import (
"context"
"fmt"
"os"
"path/filepath"
chainconfig "git.cw.tr/mukan-network/mukan-ignite/ignite/config/chain"
"git.cw.tr/mukan-network/mukan-ignite/ignite/config/chain/base"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cache"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cliui/icons"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cosmosgen"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/events"
)
type generateOptions struct {
useCache bool
isProtoVendorEnabled bool
isGoEnabled bool
isTSClientEnabled bool
isComposablesEnabled bool
isOpenAPIEnabled bool
openAPIExcludeList []string
tsClientPath string
composablesPath string
}
// GenerateTarget is a target to generate code for from proto files.
type GenerateTarget func(*generateOptions)
// GenerateGo enables generating proto based Go code needed for the chain's source code.
func GenerateGo() GenerateTarget {
return func(o *generateOptions) {
o.isGoEnabled = true
}
}
// GenerateTSClient enables generating proto based Typescript Client.
// The path assigns the output path to use for the generated Typescript client
// overriding the configured or default path. Path can be an empty string.
func GenerateTSClient(path string, useCache bool) GenerateTarget {
return func(o *generateOptions) {
o.isTSClientEnabled = true
o.tsClientPath = path
o.useCache = useCache
}
}
// GenerateComposables enables generating proto based Typescript Client and Vue 3 composables.
func GenerateComposables(path string) GenerateTarget {
return func(o *generateOptions) {
o.isTSClientEnabled = true
o.isComposablesEnabled = true
o.composablesPath = path
}
}
// GenerateOpenAPI enables generating OpenAPI spec for your chain.
func GenerateOpenAPI(excludeList []string) GenerateTarget {
return func(o *generateOptions) {
o.isOpenAPIEnabled = true
o.openAPIExcludeList = excludeList
}
}
// GenerateProtoVendor enables `proto_vendor` folder generation.
// Proto vendor is generated from Go dependencies that contain proto files that
// are not included in the app's Buf config.
// Enabling proto vendoring might update Buf config with missing dependencies
// if a Go dependency contains proto files and a Buf config with a name that is
// not listed in the Buf dependencies.
func GenerateProtoVendor() GenerateTarget {
return func(o *generateOptions) {
o.isProtoVendorEnabled = true
}
}
// generateFromConfig makes code generation from proto files from the given config.
func (c *Chain) generateFromConfig(ctx context.Context, cacheStorage cache.Storage, generateClients bool) error {
conf, err := c.Config()
if err != nil {
return err
}
// Additional code generation targets
var targets []GenerateTarget
if conf.Client.OpenAPI.Path != "" {
targets = append(targets, GenerateOpenAPI(conf.Client.OpenAPI.ExcludeList))
}
if generateClients {
if p := conf.Client.Typescript.Path; p != "" {
targets = append(targets, GenerateTSClient(p, true))
}
if p := conf.Client.Composables.Path; p != "" {
targets = append(targets, GenerateComposables(p))
}
}
// Generate proto based code for Go and optionally for any optional targets
return c.Generate(ctx, cacheStorage, GenerateGo(), targets...)
}
// Generate makes code generation from proto files for given target and additionalTargets.
func (c *Chain) Generate(
ctx context.Context,
cacheStorage cache.Storage,
target GenerateTarget,
additionalTargets ...GenerateTarget,
) error {
var targetOptions generateOptions
for _, apply := range append(additionalTargets, target) {
apply(&targetOptions)
}
conf, err := c.Config()
if err != nil {
return err
}
c.ev.Send("Building proto...", events.ProgressUpdate())
options := []cosmosgen.Option{cosmosgen.CollectEvents(c.ev)}
if targetOptions.isGoEnabled {
options = append(options, cosmosgen.WithGoGeneration())
}
if targetOptions.isProtoVendorEnabled {
options = append(options, cosmosgen.UpdateBufModule())
}
var (
openAPIPath, tsClientPath, composablesPath string
updateConfig bool
)
if targetOptions.isOpenAPIEnabled {
openAPIPath = conf.Client.OpenAPI.Path
if openAPIPath == "" {
openAPIPath = chainconfig.DefaultOpenAPIPath
}
// Non-absolute OpenAPI paths must be treated as relative to the app directory
if !filepath.IsAbs(openAPIPath) {
openAPIPath = filepath.Join(c.app.Path, openAPIPath)
}
options = append(options, cosmosgen.WithOpenAPIGeneration(openAPIPath, targetOptions.openAPIExcludeList))
}
if targetOptions.isTSClientEnabled {
tsClientPath = targetOptions.tsClientPath
if tsClientPath == "" {
tsClientPath = chainconfig.TSClientPath(*conf)
// When TS client is generated make sure the config is updated
// with the output path when the client path option is empty.
if conf.Client.Typescript.Path == "" {
conf.Client.Typescript.Path = tsClientPath
updateConfig = true
}
}
// Non-absolute TS client output paths must be treated as relative to the app directory
if !filepath.IsAbs(tsClientPath) {
tsClientPath = filepath.Join(c.app.Path, tsClientPath)
}
options = append(options,
cosmosgen.WithTSClientGeneration(
cosmosgen.TypescriptModulePath(tsClientPath),
tsClientPath,
targetOptions.useCache,
),
)
}
if targetOptions.isComposablesEnabled {
composablesPath = targetOptions.composablesPath
if composablesPath == "" {
composablesPath = chainconfig.ComposablesPath(conf)
if conf.Client.Composables.Path == "" {
conf.Client.Composables.Path = composablesPath
updateConfig = true
}
}
// Non-absolute Composables output paths must be treated as relative to the app directory
if !filepath.IsAbs(composablesPath) {
composablesPath = filepath.Join(c.app.Path, composablesPath)
}
options = append(options,
cosmosgen.WithComposablesGeneration(
cosmosgen.ComposableModulePath(composablesPath),
composablesPath,
),
)
}
if err := cosmosgen.Generate(
ctx,
cacheStorage,
c.app.Path,
conf.Build.Proto.Path,
c.app.ImportPath,
chainconfig.DefaultVuePath,
options...,
); err != nil {
return &CannotBuildAppError{err}
}
// Check if the client config options have to be updated with the paths of the generated code
if updateConfig {
if err := c.saveClientConfig(conf.Client); err != nil {
return errors.Errorf("error adding generated paths to config file: %w", err)
}
}
if c.options.printGeneratedPaths {
if targetOptions.isTSClientEnabled {
c.ev.Send(
fmt.Sprintf("Typescript client path: %s", tsClientPath),
events.Icon(icons.Bullet),
events.ProgressFinish(),
)
}
if targetOptions.isComposablesEnabled {
c.ev.Send(
fmt.Sprintf("Vue composables path: %s", composablesPath),
events.Icon(icons.Bullet),
events.ProgressFinish(),
)
}
if targetOptions.isOpenAPIEnabled {
c.ev.Send(
fmt.Sprintf("OpenAPI path: %s", openAPIPath),
events.Icon(icons.Bullet),
events.ProgressFinish(),
)
}
}
return nil
}
func (c Chain) saveClientConfig(client base.Client) error {
path := c.ConfigPath()
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// Initialize the config to the file values ignoring empty
// values that otherwise would be initialized to defaults.
// Defaults must be ignored to avoid writing them to the
// YAML config file when they are not present.
var cfg chainconfig.Config
if err := cfg.Decode(file); err != nil {
return err
}
cfg.Client = client
return chainconfig.Save(cfg, path)
}