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
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

- [#4586](https://github.com/ignite/cli/pull/4586) Remove network as default plugin.

### Bug Fixes

- [#4347](https://github.com/ignite/cli/pull/4347) Fix `ts-client` generation

## [`v28.8.2`](https://github.com/ignite/cli/releases/tag/v28.8.2)

### Changes
Expand Down
7 changes: 3 additions & 4 deletions ignite/cmd/generate_composables.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import (

func NewGenerateComposables() *cobra.Command {
c := &cobra.Command{
Hidden: true, // hidden util we have a better ts-client.
Use: "composables",
Short: "TypeScript frontend client and Vue 3 composables",
RunE: generateComposablesHandler,
Use: "composables",
Short: "TypeScript frontend client and Vue 3 composables",
RunE: generateComposablesHandler,
}

c.Flags().AddFlagSet(flagSetYes())
Expand Down
7 changes: 3 additions & 4 deletions ignite/cmd/generate_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import (

func NewGenerateHooks() *cobra.Command {
c := &cobra.Command{
Hidden: true, // hidden util we have a better ts-client.
Use: "hooks",
Short: "TypeScript frontend client and React hooks",
RunE: generateHooksHandler,
Use: "hooks",
Short: "TypeScript frontend client and React hooks",
RunE: generateHooksHandler,
}

c.Flags().AddFlagSet(flagSetYes())
Expand Down
18 changes: 10 additions & 8 deletions ignite/cmd/generate_typescript_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import (

const (
flagUseCache = "use-cache"
msgBufAuth = "Generate ts-client depends on a 'buf.build' remote plugin, and as of August 1, 2024, Buf will begin limiting remote plugin requests from unauthenticated users on 'buf.build'. If you send more than ten unauthenticated requests per hour using remote plugins, you’ll start to see rate limit errors. Please authenticate before running ts-client command using 'buf registry login' command and follow the instructions. For more info, check https://buf.build/docs/generate/auth-required."
msgBufAuth = "Generate ts-client uses a 'buf.build' remote plugin. Buf is begin limiting remote plugin requests from unauthenticated users on 'buf.build'. Intensively using this function will get you rate limited. Authenticate with 'buf registry login' to avoid this (https://buf.build/docs/generate/auth-required)."
)

func NewGenerateTSClient() *cobra.Command {
c := &cobra.Command{
Hidden: true, // hidden util we have a better ts-client.
Use: "ts-client",
Short: "TypeScript frontend client",
Use: "ts-client",
Short: "TypeScript frontend client",
Long: `Generate a framework agnostic TypeScript client for your blockchain project.

By default the TypeScript client is generated in the "ts-client/" directory. You
Expand Down Expand Up @@ -52,11 +51,14 @@ func generateTSClientHandler(cmd *cobra.Command, _ []string) error {
session := cliui.New(cliui.StartSpinnerWithText(statusGenerating))
defer session.End()

if err := session.AskConfirm(msgBufAuth); err != nil {
if errors.Is(err, promptui.ErrAbort) {
return errors.New("buf not auth")
if !getYes(cmd) {
if err := session.AskConfirm(msgBufAuth); err != nil {
if errors.Is(err, promptui.ErrAbort) {
return errors.New("buf not auth")
}

return err
}
return err
}

c, err := chain.NewWithHomeFlags(
Expand Down
9 changes: 1 addition & 8 deletions ignite/pkg/cosmosanalysis/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type moduleDiscoverer struct {
// IsCosmosSDKModulePkg check if a Go import path is a Cosmos SDK package module.
// These type of package have the "cosmossdk.io/x" prefix.
func IsCosmosSDKModulePkg(path string) bool {
return strings.Contains(path, "cosmossdk.io/x/")
return strings.Contains(path, "cosmossdk.io/x/") || strings.Contains(path, "github.com/cosmos/cosmos-sdk")
}

// Discover discovers and returns modules and their types that are registered in the app
Expand Down Expand Up @@ -280,13 +280,6 @@ func (d *moduleDiscoverer) discover(pkg protoanalysis.Package) (Module, error) {
return false
}

// do not use if an SDK message.
for _, msg := range msgs {
if msg == protomsg.Name {
return false
}
}

// do not use if used as a request/return type of RPC.
for _, s := range pkg.Services {
for _, q := range s.RPCFuncs {
Expand Down
100 changes: 50 additions & 50 deletions ignite/pkg/cosmosgen/generate_typescript.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ type tsGenerator struct {
}

type generatePayload struct {
Modules []module.Module
PackageNS string
IsConsumerChain bool
Modules []module.Module
PackageNS string
}

func newTSGenerator(g *generator) *tsGenerator {
Expand All @@ -44,23 +43,10 @@ func (g *generator) generateTS(ctx context.Context) error {

appModulePath := gomodulepath.ExtractAppPath(chainPath.RawPath)
data := generatePayload{
Modules: g.appModules,
PackageNS: strings.ReplaceAll(appModulePath, "/", "-"),
IsConsumerChain: false,
Modules: g.appModules,
PackageNS: strings.ReplaceAll(appModulePath, "/", "-"),
}

// Third party modules are always required to generate the root
// template because otherwise it would be generated only with
// custom modules losing the registration of the third party
// modules when the root templates are re-generated.
for _, modules := range g.thirdModules {
data.Modules = append(data.Modules, modules...)
for _, m := range modules {
if strings.HasPrefix(m.Pkg.Name, "interchain_security.ccv.consumer") {
data.IsConsumerChain = true
}
}
}
// Make sure the modules are always sorted to keep the import
// and module registration order consistent so the generated
// files are not changed.
Expand All @@ -77,47 +63,50 @@ func (g *generator) generateTS(ctx context.Context) error {
}

func (g *tsGenerator) generateModuleTemplates(ctx context.Context) error {
gg := &errgroup.Group{}
dirCache := cache.New[[]byte](g.g.cacheStorage, dirchangeCacheNamespace)
add := func(sourcePath string, modules []module.Module) {
for _, m := range modules {
m := m
gg.Go(func() error {
cacheKey := m.Pkg.Path
paths := []string{m.Pkg.Path, g.g.opts.jsOut(m)}

// Always generate module templates by default unless cache is enabled, in which
// case the module template is generated when one or more files were changed in
// the module since the last generation.
if g.g.opts.useCache {
changed, err := dirchange.HasDirChecksumChanged(dirCache, cacheKey, sourcePath, paths...)
if err != nil {
return err
}

if !changed {
return nil
}
}

if err := g.generateModuleTemplate(ctx, sourcePath, m); err != nil {
return err
}

return dirchange.SaveDirChecksum(dirCache, cacheKey, sourcePath, paths...)
})
add := func(sourcePath string, m module.Module) error {
cacheKey := m.Pkg.Path
paths := []string{m.Pkg.Path, g.g.opts.jsOut(m)}

// Always generate module templates by default unless cache is enabled, in which
// case the module template is generated when one or more files were changed in
// the module since the last generation.
if g.g.opts.useCache {
changed, err := dirchange.HasDirChecksumChanged(dirCache, cacheKey, sourcePath, paths...)
if err != nil {
return err
}

if !changed {
return nil
}
}

if err := g.generateModuleTemplate(ctx, sourcePath, m); err != nil {
return err
}

return dirchange.SaveDirChecksum(dirCache, cacheKey, sourcePath, paths...)
}

add(g.g.appPath, g.g.appModules)
gg := &errgroup.Group{}
for _, m := range g.g.appModules {
gg.Go(func() error {
return add(g.g.appPath, m)
})
}

// Always generate third party modules; This is required because not generating them might
// lead to issues with the module registration in the root template. The root template must
// always be generated with 3rd party modules which means that if a new 3rd party module
// is available and not generated it would lead to the registration of a new not generated
// 3rd party module.
for sourcePath, modules := range g.g.thirdModules {
add(sourcePath, modules)
for _, m := range modules {
gg.Go(func() error {
return add(sourcePath, m)
})
}
}

return gg.Wait()
Expand All @@ -132,6 +121,7 @@ func (g *tsGenerator) generateModuleTemplate(
out = g.g.opts.jsOut(m)
typesOut = filepath.Join(out, "types")
)

if err := os.MkdirAll(typesOut, 0o766); err != nil {
return err
}
Expand All @@ -141,7 +131,7 @@ func (g *tsGenerator) generateModuleTemplate(

// All "cosmossdk.io" module packages must use SDK's
// proto path which is where the proto files are stored.
protoPath := filepath.Join(g.g.appPath, g.g.protoDir)
protoPath := filepath.Join(appPath, g.g.protoDir) // use module app path
if module.IsCosmosSDKModulePkg(appPath) {
protoPath = filepath.Join(g.g.sdkDir, "proto")
}
Expand All @@ -158,7 +148,17 @@ func (g *tsGenerator) generateModuleTemplate(
return err
}

return templateTSClientModule.Write(out, protoPath, struct {
// Generate the module template
if err := templateTSClientModule.Write(out, protoPath, struct {
Module module.Module
}{
Module: m,
}); err != nil {
return err
}

// Generate the rest API template (using axios)
return templateTSClientRest.Write(out, protoPath, struct {
Module module.Module
}{
Module: m,
Expand Down
1 change: 1 addition & 0 deletions ignite/pkg/cosmosgen/generate_typescript_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cosmosgen_test
9 changes: 7 additions & 2 deletions ignite/pkg/cosmosgen/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
templateTSClientModule = newTemplateWriter("module")
templateTSClientVue = newTemplateWriter("vue")
templateTSClientVueRoot = newTemplateWriter("vue-root")
templateTSClientRest = newTemplateWriter("rest")
templateTSClientComposable = newTemplateWriter("composable")
templateTSClientComposableRoot = newTemplateWriter("composable-root")
)
Expand Down Expand Up @@ -69,9 +70,13 @@ func (t templateWriter) Write(destDir, protoPath string, data interface{}) error
return strcase.ToCamel(replacer.Replace(word))
},
"resolveFile": func(fullPath string) string {
rel, _ := filepath.Rel(protoPath, fullPath)
_ = protoPath // eventually, we should use the proto folder name of this, for the application (but not for the other modules)

res := strings.Split(fullPath, "proto/")
rel := res[len(res)-1] // get path after proto/
rel = strings.TrimSuffix(rel, ".proto")
return rel

return "./types/" + rel
},
"inc": func(i int) int {
return i + 1
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosgen/templates/module/module.ts.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { msgTypes } from './registry';
import { IgniteClient } from "../client"
import { MissingWalletError } from "../helpers"
import { Api } from "./rest";
{{ range .Module.Msgs }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}";
{{ range .Module.Msgs }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}";
{{ end }}
{{ range .Module.Types }}import { {{ .Name }} as type{{- .Name -}} } from "./types"
{{ end }}
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosgen/templates/module/registry.ts.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GeneratedType } from "@cosmjs/proto-signing";
{{ range .Module.Msgs }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}";
{{ range .Module.Msgs }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}";
{{ end }}
const msgTypes: Array<[string, GeneratedType]> = [
{{ range .Module.Msgs }}["/{{ .URI }}", {{ .Name }}],
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosgen/templates/module/types.ts.tpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{ range .Module.Types }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}"
{{ range .Module.Types }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}"
{{ end }}

export {
Expand Down
45 changes: 45 additions & 0 deletions ignite/pkg/cosmosgen/templates/rest/rest.ts.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint-disable */
import axios from 'axios'
import * as qs from 'qs'

// this is used to derive the proper return types for query endpoints
export type BaseQueryClient = {
queryBalances: (address: string, params?: any) => Promise<any>
}

export class Api {
private axios: any
private baseURL: string

constructor({ baseURL }: { baseURL: string }) {
this.baseURL = baseURL
this.axios = axios.create({
baseURL,
timeout: 30000,
paramsSerializer: function(params: any) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
})
}

// common helper for most simple operations
private async handleRequest(url: string, params?: any): Promise<any> {
try {
const response = await this.axios.get(url, { params })
return response
} catch (e: any) {
if (e.response?.data) {
console.error('Error in API request:', e.response.data)
}
throw e
}
}

// Return URL for specific module endpoints
public getModuleEndpoint(endpoint: string): string {
return `${this.baseURL}/${endpoint}`
}

// Methods for specific module endpoints can be added here
// The actual methods will be auto-generated from OpenAPI specs in a real implementation
}
7 changes: 1 addition & 6 deletions ignite/pkg/cosmosgen/templates/root/client.ts.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ export class IgniteClient extends EventEmitter {
).queryClient;
const bankQueryClient = (await import("./cosmos.bank.v1beta1/module"))
.queryClient;
{{ if eq .IsConsumerChain false }}
const stakingQueryClient = (await import("./cosmos.staking.v1beta1/module")).queryClient;
const stakingqc = stakingQueryClient({ addr: this.env.apiURL });
const staking = await (await stakingqc.queryParams()).data;
{{ end }}
const qc = queryClient({ addr: this.env.apiURL });
const node_info = await (await qc.serviceGetNodeInfo()).data;
const chainId = node_info.default_node_info?.network ?? "";
Expand Down Expand Up @@ -116,15 +114,12 @@ export class IgniteClient extends EventEmitter {
return y;
}) ?? [];

{{ if eq .IsConsumerChain true -}}
let stakeCurrency = currencies.find((x) => !x.coinDenom.startsWith("ibc/"));
{{ else }}
let stakeCurrency = {
coinDenom: staking.params?.bond_denom?.toUpperCase() ?? "",
coinMinimalDenom: staking.params?.bond_denom ?? "",
coinDecimals: 0,
};
{{ end }}

let feeCurrencies =
tokens.supply?.map((x) => {
const y = {
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosgen/templates/root/index.ts.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Registry } from '@cosmjs/proto-signing'
import { IgniteClient } from "./client";
import { MissingWalletError } from "./helpers";
{{ range .Modules }}import { IgntModule as {{ camelCaseUpperSta .Pkg.Name }}, msgTypes as {{ camelCaseUpperSta .Pkg.Name }}MsgTypes } from './{{ .Pkg.Name }}'
{{ range .Modules }}import { IgntModule as {{ camelCaseUpperSta .Pkg.Name }}, msgTypes as {{ camelCaseUpperSta .Pkg.Name }}MsgTypes } from './{{ replace .Pkg.Name "." "/" }}'
{{ end }}

const Client = IgniteClient.plugin([
Expand Down
Loading