mukan-ignite/ignite/services/scaffolder/module.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

327 lines
8.4 KiB
Go

package scaffolder
import (
"go/token"
"os"
"path/filepath"
"strings"
"github.com/gobuffalo/genny/v2"
appanalysis "github.com/ignite/cli/v29/ignite/pkg/cosmosanalysis/app"
"github.com/ignite/cli/v29/ignite/pkg/errors"
"github.com/ignite/cli/v29/ignite/pkg/multiformatname"
"github.com/ignite/cli/v29/ignite/templates/field"
"github.com/ignite/cli/v29/ignite/templates/module"
modulecreate "github.com/ignite/cli/v29/ignite/templates/module/create"
)
const (
moduleDir = "x"
modulePkg = "module"
)
var (
// reservedNames are either names from the default modules defined in a Cosmos-SDK app or names used in the default query and tx CLI namespace.
// A new module's name can't be equal to a reserved name.
// A map is used for direct comparing.
reservedNames = map[string]struct{}{
"account": {},
"block": {},
"broadcast": {},
"encode": {},
"multisign": {},
"sign": {},
"tx": {},
"txs": {},
"consumer": {}, // ICS consumer module
"ccvconsumer": {}, // ICS consumer module
"CCV": {}, // ICS consumer module
"capability": {},
"auth": {},
"bank": {},
"distribution": {},
"staking": {},
"slashing": {},
"gov": {},
"mint": {},
"ibc": {},
"genutil": {},
"evidence": {},
"authz": {},
"transfer": {}, // IBC transfer
"interchainaccounts": {},
"feeibc": {},
"feegrant": {},
"params": {},
"upgrade": {},
"vesting": {},
"circuit": {},
"nft": {},
"group": {},
"consensus": {},
"epochs": {},
"protocolpool": {},
}
// defaultStoreKeys are the names of the default store keys defined in a Cosmos-SDK app.
// A new module's name can't have a defined store key in its prefix because of potential store key collision.
defaultStoreKeys = []string{
"capability",
"acc", // auth module
"bank",
"distribution",
"staking",
"slashing",
"gov",
"mint",
"ibc",
"transfer", // IBC transfer
"feeibc",
"evidence",
"feegrant",
"params",
"upgrade",
"circuit",
"nft",
"group",
"consensus",
}
)
// moduleCreationOptions holds options for creating a new module.
type moduleCreationOptions struct {
// ibc true if the module is an ibc module.
ibc bool
// params list of parameters.
params []string
// moduleConfigs list of module configs.
moduleConfigs []string
// ibcChannelOrdering ibc channel ordering.
ibcChannelOrdering string
// dependencies list of module dependencies.
dependencies []modulecreate.Dependency
}
// ModuleCreationOption configures Chain.
type ModuleCreationOption func(*moduleCreationOptions)
// WithIBC scaffolds a module with IBC enabled.
func WithIBC() ModuleCreationOption {
return func(m *moduleCreationOptions) {
m.ibc = true
}
}
// WithParams scaffolds a module with params.
func WithParams(params []string) ModuleCreationOption {
return func(m *moduleCreationOptions) {
m.params = params
}
}
// WithModuleConfigs scaffolds a module with module configs.
func WithModuleConfigs(moduleConfigs []string) ModuleCreationOption {
return func(m *moduleCreationOptions) {
m.moduleConfigs = moduleConfigs
}
}
// WithIBCChannelOrdering configures channel ordering of the IBC module.
func WithIBCChannelOrdering(ordering string) ModuleCreationOption {
return func(m *moduleCreationOptions) {
switch ordering {
case "ordered":
m.ibcChannelOrdering = "ORDERED"
case "unordered":
m.ibcChannelOrdering = "UNORDERED"
default:
m.ibcChannelOrdering = "NONE"
}
}
}
// WithDependencies specifies the name of the modules that the module depends on.
func WithDependencies(dependencies []modulecreate.Dependency) ModuleCreationOption {
return func(m *moduleCreationOptions) {
m.dependencies = dependencies
}
}
// CreateModule creates a new empty module in the scaffolded app.
func (s Scaffolder) CreateModule(
moduleName string,
options ...ModuleCreationOption,
) error {
mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber)
if err != nil {
return err
}
moduleName = mfName.LowerCase
// Check if the module name is valid
if err := checkModuleName(s.appPath, moduleName); err != nil {
return err
}
// Check if the module already exist
ok, err := moduleExists(s.appPath, moduleName)
if err != nil {
return err
}
if ok {
return errors.Errorf("the module %v already exists", moduleName)
}
// Apply the options
var creationOpts moduleCreationOptions
for _, apply := range options {
apply(&creationOpts)
}
// Parse params with the associated type
params, err := field.ParseFields(creationOpts.params, checkForbiddenTypeIndex)
if err != nil {
return err
}
// Parse configs with the associated type
configs, err := field.ParseFields(creationOpts.moduleConfigs, checkForbiddenTypeIndex)
if err != nil {
return err
}
// Check dependencies
if err := checkDependencies(creationOpts.dependencies, s.appPath); err != nil {
return err
}
opts := &modulecreate.CreateOptions{
ModuleName: moduleName,
ModulePath: s.modpath.RawPath,
Params: params,
Configs: configs,
AppName: s.modpath.Package,
AppPath: s.appPath,
ProtoDir: s.protoDir,
ProtoVer: "v1", // TODO(@julienrbrt): possibly in the future add flag to specify custom proto version.
IsIBC: creationOpts.ibc,
IBCOrdering: creationOpts.ibcChannelOrdering,
Dependencies: creationOpts.dependencies,
}
g, err := modulecreate.NewGenerator(opts)
if err != nil {
return err
}
gens := []*genny.Generator{g}
// Scaffold IBC module
if opts.IsIBC {
g, err = modulecreate.NewIBC(opts)
if err != nil {
return err
}
gens = append(gens, g)
}
gens = append(gens, modulecreate.NewAppModify(opts))
err = s.Run(gens...)
var validationErr errors.ValidationError
if err != nil && !errors.As(err, &validationErr) {
return err
}
return nil
}
// moduleExists checks if the module exists in the app.
func moduleExists(appPath string, moduleName string) (bool, error) {
absPath, err := filepath.Abs(filepath.Join(appPath, moduleDir, moduleName))
if err != nil {
return false, err
}
_, err = os.Stat(absPath)
if os.IsNotExist(err) {
// The module doesn't exist
return false, nil
}
return err == nil, err
}
// checkModuleName checks if the name can be used as a module name.
func checkModuleName(appPath, moduleName string) error {
// go keyword
if token.Lookup(moduleName).IsKeyword() {
return errors.Errorf("%s is a Go keyword", moduleName)
}
// check if the name is a reserved name
if _, ok := reservedNames[moduleName]; ok {
return errors.Errorf("%s is a reserved name and can't be used as a module name", moduleName)
}
checkPrefix := func(name, prefix string) error {
if strings.HasPrefix(name, prefix) {
return errors.Errorf("the module name can't be prefixed with %s because of potential store key collision", prefix)
}
return nil
}
// check if the name can imply potential store key collision
for _, defaultStoreKey := range defaultStoreKeys {
if err := checkPrefix(moduleName, defaultStoreKey); err != nil {
return err
}
}
// check store key with user's defined modules
// we consider all user's defined modules use the module name as the store key
entries, err := os.ReadDir(filepath.Join(appPath, moduleDir))
if os.IsNotExist(err) {
return nil
}
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
if err := checkPrefix(moduleName, entry.Name()); err != nil {
return err
}
}
return nil
}
// checkDependencies perform checks on the dependencies.
func checkDependencies(dependencies []modulecreate.Dependency, appPath string) error {
depMap := make(map[string]struct{})
for _, dep := range dependencies {
// check the dependency has been registered
path := filepath.Join(appPath, module.PathAppModule)
if err := appanalysis.CheckKeeper(path, dep.KeeperName()); err != nil {
return errors.Errorf(
"the module cannot have %s as a dependency: %w",
dep.Name,
err,
)
}
// check duplicated
_, ok := depMap[dep.Name]
if ok {
return errors.Errorf("%s is a duplicated dependency", dep)
}
depMap[dep.Name] = struct{}{}
}
return nil
}