Skip to content

Commit e769caa

Browse files
fix(cosmosgen): fix ts-client (backport #4347) (#4634)
* fix(cosmosgen): fix ts-client (#4347) * fix(cosmosgen): fix ts-client * cl * fix path * add axios + fix typescript path * simplify * updates * rm consensus chain specific logic * fix * fix templates * fix type discovery * lint * remove broken disclaimer from docs * add error goup at correct place * lint (cherry picked from commit 1fc0b9a) # Conflicts: # ignite/cmd/generate_typescript_client.go # ignite/pkg/cosmosgen/generate_typescript.go # ignite/pkg/cosmosgen/template.go * updates --------- Co-authored-by: julienrbrt <[email protected]>
1 parent 5576ef0 commit e769caa

14 files changed

+129
-86
lines changed

changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

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

13+
### Bug Fixes
14+
15+
- [#4347](https://github.com/ignite/cli/pull/4347) Fix `ts-client` generation
16+
1317
## [`v28.8.2`](https://github.com/ignite/cli/releases/tag/v28.8.2)
1418

1519
### Changes

ignite/cmd/generate_composables.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import (
1010

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

1918
c.Flags().AddFlagSet(flagSetYes())

ignite/cmd/generate_hooks.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import (
1010

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

1918
c.Flags().AddFlagSet(flagSetYes())

ignite/cmd/generate_typescript_client.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ import (
1212

1313
const (
1414
flagUseCache = "use-cache"
15-
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."
15+
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)."
1616
)
1717

1818
func NewGenerateTSClient() *cobra.Command {
1919
c := &cobra.Command{
20-
Hidden: true, // hidden util we have a better ts-client.
21-
Use: "ts-client",
22-
Short: "TypeScript frontend client",
20+
Use: "ts-client",
21+
Short: "TypeScript frontend client",
2322
Long: `Generate a framework agnostic TypeScript client for your blockchain project.
2423
2524
By default the TypeScript client is generated in the "ts-client/" directory. You
@@ -52,11 +51,14 @@ func generateTSClientHandler(cmd *cobra.Command, _ []string) error {
5251
session := cliui.New(cliui.StartSpinnerWithText(statusGenerating))
5352
defer session.End()
5453

55-
if err := session.AskConfirm(msgBufAuth); err != nil {
56-
if errors.Is(err, promptui.ErrAbort) {
57-
return errors.New("buf not auth")
54+
if !getYes(cmd) {
55+
if err := session.AskConfirm(msgBufAuth); err != nil {
56+
if errors.Is(err, promptui.ErrAbort) {
57+
return errors.New("buf not auth")
58+
}
59+
60+
return err
5861
}
59-
return err
6062
}
6163

6264
c, err := chain.NewWithHomeFlags(

ignite/pkg/cosmosanalysis/module/module.go

+1-8
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type moduleDiscoverer struct {
8787
// IsCosmosSDKModulePkg check if a Go import path is a Cosmos SDK package module.
8888
// These type of package have the "cosmossdk.io/x" prefix.
8989
func IsCosmosSDKModulePkg(path string) bool {
90-
return strings.Contains(path, "cosmossdk.io/x/")
90+
return strings.Contains(path, "cosmossdk.io/x/") || strings.Contains(path, "github.com/cosmos/cosmos-sdk")
9191
}
9292

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

283-
// do not use if an SDK message.
284-
for _, msg := range msgs {
285-
if msg == protomsg.Name {
286-
return false
287-
}
288-
}
289-
290283
// do not use if used as a request/return type of RPC.
291284
for _, s := range pkg.Services {
292285
for _, q := range s.RPCFuncs {

ignite/pkg/cosmosgen/generate_typescript.go

+50-50
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ type tsGenerator struct {
2323
}
2424

2525
type generatePayload struct {
26-
Modules []module.Module
27-
PackageNS string
28-
IsConsumerChain bool
26+
Modules []module.Module
27+
PackageNS string
2928
}
3029

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

4544
appModulePath := gomodulepath.ExtractAppPath(chainPath.RawPath)
4645
data := generatePayload{
47-
Modules: g.appModules,
48-
PackageNS: strings.ReplaceAll(appModulePath, "/", "-"),
49-
IsConsumerChain: false,
46+
Modules: g.appModules,
47+
PackageNS: strings.ReplaceAll(appModulePath, "/", "-"),
5048
}
5149

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

7965
func (g *tsGenerator) generateModuleTemplates(ctx context.Context) error {
80-
gg := &errgroup.Group{}
8166
dirCache := cache.New[[]byte](g.g.cacheStorage, dirchangeCacheNamespace)
82-
add := func(sourcePath string, modules []module.Module) {
83-
for _, m := range modules {
84-
m := m
85-
gg.Go(func() error {
86-
cacheKey := m.Pkg.Path
87-
paths := []string{m.Pkg.Path, g.g.opts.jsOut(m)}
88-
89-
// Always generate module templates by default unless cache is enabled, in which
90-
// case the module template is generated when one or more files were changed in
91-
// the module since the last generation.
92-
if g.g.opts.useCache {
93-
changed, err := dirchange.HasDirChecksumChanged(dirCache, cacheKey, sourcePath, paths...)
94-
if err != nil {
95-
return err
96-
}
97-
98-
if !changed {
99-
return nil
100-
}
101-
}
102-
103-
if err := g.generateModuleTemplate(ctx, sourcePath, m); err != nil {
104-
return err
105-
}
106-
107-
return dirchange.SaveDirChecksum(dirCache, cacheKey, sourcePath, paths...)
108-
})
67+
add := func(sourcePath string, m module.Module) error {
68+
cacheKey := m.Pkg.Path
69+
paths := []string{m.Pkg.Path, g.g.opts.jsOut(m)}
70+
71+
// Always generate module templates by default unless cache is enabled, in which
72+
// case the module template is generated when one or more files were changed in
73+
// the module since the last generation.
74+
if g.g.opts.useCache {
75+
changed, err := dirchange.HasDirChecksumChanged(dirCache, cacheKey, sourcePath, paths...)
76+
if err != nil {
77+
return err
78+
}
79+
80+
if !changed {
81+
return nil
82+
}
10983
}
84+
85+
if err := g.generateModuleTemplate(ctx, sourcePath, m); err != nil {
86+
return err
87+
}
88+
89+
return dirchange.SaveDirChecksum(dirCache, cacheKey, sourcePath, paths...)
11090
}
11191

112-
add(g.g.appPath, g.g.appModules)
92+
gg := &errgroup.Group{}
93+
for _, m := range g.g.appModules {
94+
gg.Go(func() error {
95+
return add(g.g.appPath, m)
96+
})
97+
}
11398

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

123112
return gg.Wait()
@@ -132,6 +121,7 @@ func (g *tsGenerator) generateModuleTemplate(
132121
out = g.g.opts.jsOut(m)
133122
typesOut = filepath.Join(out, "types")
134123
)
124+
135125
if err := os.MkdirAll(typesOut, 0o766); err != nil {
136126
return err
137127
}
@@ -141,7 +131,7 @@ func (g *tsGenerator) generateModuleTemplate(
141131

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

161-
return templateTSClientModule.Write(out, protoPath, struct {
151+
// Generate the module template
152+
if err := templateTSClientModule.Write(out, protoPath, struct {
153+
Module module.Module
154+
}{
155+
Module: m,
156+
}); err != nil {
157+
return err
158+
}
159+
160+
// Generate the rest API template (using axios)
161+
return templateTSClientRest.Write(out, protoPath, struct {
162162
Module module.Module
163163
}{
164164
Module: m,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package cosmosgen_test

ignite/pkg/cosmosgen/template.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var (
2020
templateTSClientModule = newTemplateWriter("module")
2121
templateTSClientVue = newTemplateWriter("vue")
2222
templateTSClientVueRoot = newTemplateWriter("vue-root")
23+
templateTSClientRest = newTemplateWriter("rest")
2324
templateTSClientComposable = newTemplateWriter("composable")
2425
templateTSClientComposableRoot = newTemplateWriter("composable-root")
2526
)
@@ -69,9 +70,13 @@ func (t templateWriter) Write(destDir, protoPath string, data interface{}) error
6970
return strcase.ToCamel(replacer.Replace(word))
7071
},
7172
"resolveFile": func(fullPath string) string {
72-
rel, _ := filepath.Rel(protoPath, fullPath)
73+
_ = protoPath // eventually, we should use the proto folder name of this, for the application (but not for the other modules)
74+
75+
res := strings.Split(fullPath, "proto/")
76+
rel := res[len(res)-1] // get path after proto/
7377
rel = strings.TrimSuffix(rel, ".proto")
74-
return rel
78+
79+
return "./types/" + rel
7580
},
7681
"inc": func(i int) int {
7782
return i + 1

ignite/pkg/cosmosgen/templates/module/module.ts.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { msgTypes } from './registry';
66
import { IgniteClient } from "../client"
77
import { MissingWalletError } from "../helpers"
88
import { Api } from "./rest";
9-
{{ range .Module.Msgs }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}";
9+
{{ range .Module.Msgs }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}";
1010
{{ end }}
1111
{{ range .Module.Types }}import { {{ .Name }} as type{{- .Name -}} } from "./types"
1212
{{ end }}

ignite/pkg/cosmosgen/templates/module/registry.ts.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { GeneratedType } from "@cosmjs/proto-signing";
2-
{{ range .Module.Msgs }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}";
2+
{{ range .Module.Msgs }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}";
33
{{ end }}
44
const msgTypes: Array<[string, GeneratedType]> = [
55
{{ range .Module.Msgs }}["/{{ .URI }}", {{ .Name }}],

ignite/pkg/cosmosgen/templates/module/types.ts.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{{ range .Module.Types }}import { {{ .Name }} } from "./types/{{ resolveFile .FilePath }}"
1+
{{ range .Module.Types }}import { {{ .Name }} } from "{{ resolveFile .FilePath }}"
22
{{ end }}
33

44
export {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable */
2+
import axios from 'axios'
3+
import * as qs from 'qs'
4+
5+
// this is used to derive the proper return types for query endpoints
6+
export type BaseQueryClient = {
7+
queryBalances: (address: string, params?: any) => Promise<any>
8+
}
9+
10+
export class Api {
11+
private axios: any
12+
private baseURL: string
13+
14+
constructor({ baseURL }: { baseURL: string }) {
15+
this.baseURL = baseURL
16+
this.axios = axios.create({
17+
baseURL,
18+
timeout: 30000,
19+
paramsSerializer: function(params: any) {
20+
return qs.stringify(params, { arrayFormat: 'repeat' })
21+
}
22+
})
23+
}
24+
25+
// common helper for most simple operations
26+
private async handleRequest(url: string, params?: any): Promise<any> {
27+
try {
28+
const response = await this.axios.get(url, { params })
29+
return response
30+
} catch (e: any) {
31+
if (e.response?.data) {
32+
console.error('Error in API request:', e.response.data)
33+
}
34+
throw e
35+
}
36+
}
37+
38+
// Return URL for specific module endpoints
39+
public getModuleEndpoint(endpoint: string): string {
40+
return `${this.baseURL}/${endpoint}`
41+
}
42+
43+
// Methods for specific module endpoints can be added here
44+
// The actual methods will be auto-generated from OpenAPI specs in a real implementation
45+
}

ignite/pkg/cosmosgen/templates/root/client.ts.tpl

+1-6
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,9 @@ export class IgniteClient extends EventEmitter {
7878
).queryClient;
7979
const bankQueryClient = (await import("./cosmos.bank.v1beta1/module"))
8080
.queryClient;
81-
{{ if eq .IsConsumerChain false }}
8281
const stakingQueryClient = (await import("./cosmos.staking.v1beta1/module")).queryClient;
8382
const stakingqc = stakingQueryClient({ addr: this.env.apiURL });
8483
const staking = await (await stakingqc.queryParams()).data;
85-
{{ end }}
8684
const qc = queryClient({ addr: this.env.apiURL });
8785
const node_info = await (await qc.serviceGetNodeInfo()).data;
8886
const chainId = node_info.default_node_info?.network ?? "";
@@ -116,15 +114,12 @@ export class IgniteClient extends EventEmitter {
116114
return y;
117115
}) ?? [];
118116

119-
{{ if eq .IsConsumerChain true -}}
120-
let stakeCurrency = currencies.find((x) => !x.coinDenom.startsWith("ibc/"));
121-
{{ else }}
122117
let stakeCurrency = {
123118
coinDenom: staking.params?.bond_denom?.toUpperCase() ?? "",
124119
coinMinimalDenom: staking.params?.bond_denom ?? "",
125120
coinDecimals: 0,
126121
};
127-
{{ end }}
122+
128123
let feeCurrencies =
129124
tokens.supply?.map((x) => {
130125
const y = {

ignite/pkg/cosmosgen/templates/root/index.ts.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Registry } from '@cosmjs/proto-signing'
33
import { IgniteClient } from "./client";
44
import { MissingWalletError } from "./helpers";
5-
{{ range .Modules }}import { IgntModule as {{ camelCaseUpperSta .Pkg.Name }}, msgTypes as {{ camelCaseUpperSta .Pkg.Name }}MsgTypes } from './{{ .Pkg.Name }}'
5+
{{ range .Modules }}import { IgntModule as {{ camelCaseUpperSta .Pkg.Name }}, msgTypes as {{ camelCaseUpperSta .Pkg.Name }}MsgTypes } from './{{ replace .Pkg.Name "." "/" }}'
66
{{ end }}
77

88
const Client = IgniteClient.plugin([

0 commit comments

Comments
 (0)