From b1d7f0ff7b96ed19f3f69cb7b0c589ee9ffd7d7a Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 10 Mar 2026 21:16:49 -0300 Subject: [PATCH] add migration template --- ignite/cmd/scaffold.go | 1 + ignite/cmd/scaffold_migration.go | 78 ++++ ignite/services/scaffolder/migration.go | 68 ++++ .../{{migrationName}}.go.plush | 8 + .../templates/module/migration/migration.go | 40 ++ ignite/templates/module/migration/module.go | 344 ++++++++++++++++++ .../templates/module/migration/module_test.go | 136 +++++++ ignite/templates/module/migration/options.go | 52 +++ .../templates/module/migration/templates.go | 6 + 9 files changed, 733 insertions(+) create mode 100644 ignite/cmd/scaffold_migration.go create mode 100644 ignite/services/scaffolder/migration.go create mode 100644 ignite/templates/module/migration/files/x/{{moduleName}}/migrations/{{migrationVersion}}/{{migrationName}}.go.plush create mode 100644 ignite/templates/module/migration/migration.go create mode 100644 ignite/templates/module/migration/module.go create mode 100644 ignite/templates/module/migration/module_test.go create mode 100644 ignite/templates/module/migration/options.go create mode 100644 ignite/templates/module/migration/templates.go diff --git a/ignite/cmd/scaffold.go b/ignite/cmd/scaffold.go index 275ec9e58c..77c0566680 100644 --- a/ignite/cmd/scaffold.go +++ b/ignite/cmd/scaffold.go @@ -105,6 +105,7 @@ with an "--ibc" flag. Note that the default module is not IBC-enabled. NewScaffoldTypeList(), NewScaffoldChain(), NewScaffoldModule(), + NewScaffoldMigration(), NewScaffoldList(), NewScaffoldMap(), NewScaffoldSingle(), diff --git a/ignite/cmd/scaffold_migration.go b/ignite/cmd/scaffold_migration.go new file mode 100644 index 0000000000..292bc88e79 --- /dev/null +++ b/ignite/cmd/scaffold_migration.go @@ -0,0 +1,78 @@ +package ignitecmd + +import ( + "github.com/spf13/cobra" + + "github.com/ignite/cli/v29/ignite/pkg/cache" + "github.com/ignite/cli/v29/ignite/pkg/cliui" + "github.com/ignite/cli/v29/ignite/pkg/xgenny" + "github.com/ignite/cli/v29/ignite/services/scaffolder" +) + +// NewScaffoldMigration returns the command to scaffold a module migration. +func NewScaffoldMigration() *cobra.Command { + c := &cobra.Command{ + Use: "migration [module] [name]", + Short: "Module migration boilerplate", + Long: `Scaffold no-op migration boilerplate for an existing Cosmos SDK module. + +This command creates a new migration file in "x//migrations/vN/", +increments the module consensus version, and registers the new migration handler +inside "x//module/module.go".`, + Args: cobra.ExactArgs(2), + PreRunE: migrationPreRunHandler, + RunE: scaffoldMigrationHandler, + } + + flagSetPath(c) + c.Flags().AddFlagSet(flagSetYes()) + + return c +} + +func scaffoldMigrationHandler(cmd *cobra.Command, args []string) error { + var ( + moduleName = args[0] + migrationName = args[1] + appPath = flagGetPath(cmd) + ) + + session := cliui.New( + cliui.StartSpinnerWithText(statusScaffolding), + cliui.WithoutUserInteraction(getYes(cmd)), + ) + defer session.End() + + cfg, _, err := getChainConfig(cmd) + if err != nil { + return err + } + + sc, err := scaffolder.New(cmd.Context(), appPath, cfg.Build.Proto.Path) + if err != nil { + return err + } + + if err := sc.CreateModuleMigration(moduleName, migrationName); err != nil { + return err + } + + sm, err := sc.ApplyModifications(xgenny.ApplyPreRun(scaffolder.AskOverwriteFiles(session))) + if err != nil { + return err + } + + if err := sc.PostScaffold(cmd.Context(), cache.Storage{}, true); err != nil { + return err + } + + modificationsStr, err := sm.String() + if err != nil { + return err + } + + session.Println(modificationsStr) + session.Printf("\nšŸŽ‰ Migration %s added to module %s.\n\n", migrationName, moduleName) + + return nil +} diff --git a/ignite/services/scaffolder/migration.go b/ignite/services/scaffolder/migration.go new file mode 100644 index 0000000000..192339efe7 --- /dev/null +++ b/ignite/services/scaffolder/migration.go @@ -0,0 +1,68 @@ +package scaffolder + +import ( + "os" + "path/filepath" + + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/pkg/multiformatname" + modulemigration "github.com/ignite/cli/v29/ignite/templates/module/migration" +) + +// CreateModuleMigration scaffolds a new module migration inside an existing module. +func (s Scaffolder) CreateModuleMigration(moduleName, migrationName string) error { + mfModuleName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber) + if err != nil { + return err + } + moduleName = mfModuleName.LowerCase + + mfMigrationName, err := multiformatname.NewName(migrationName) + if err != nil { + return err + } + if err := checkGoReservedWord(mfMigrationName.LowerCamel); err != nil { + return errors.Errorf("%s can't be used as a migration name: %w", migrationName, err) + } + + ok, err := moduleExists(s.appPath, moduleName) + if err != nil { + return err + } + if !ok { + return errors.Errorf("the module %s doesn't exist", moduleName) + } + + moduleFilePath := filepath.Join(s.appPath, moduleDir, moduleName, modulePkg, "module.go") + content, err := os.ReadFile(moduleFilePath) + if err != nil { + return err + } + + fromVersion, err := modulemigration.ConsensusVersion(string(content)) + if err != nil { + return err + } + + opts := &modulemigration.Options{ + ModuleName: moduleName, + ModulePath: s.modpath.RawPath, + MigrationName: mfMigrationName, + FromVersion: fromVersion, + ToVersion: fromVersion + 1, + } + + versionDir := filepath.Join(s.appPath, opts.MigrationDir()) + if _, err := os.Stat(versionDir); err == nil { + return errors.Errorf("migration version %s already exists for module %s", opts.MigrationVersion(), moduleName) + } else if !os.IsNotExist(err) { + return err + } + + g, err := modulemigration.NewGenerator(opts) + if err != nil { + return err + } + + return s.Run(g) +} diff --git a/ignite/templates/module/migration/files/x/{{moduleName}}/migrations/{{migrationVersion}}/{{migrationName}}.go.plush b/ignite/templates/module/migration/files/x/{{moduleName}}/migrations/{{migrationVersion}}/{{migrationName}}.go.plush new file mode 100644 index 0000000000..174316e43c --- /dev/null +++ b/ignite/templates/module/migration/files/x/{{moduleName}}/migrations/{{migrationVersion}}/{{migrationName}}.go.plush @@ -0,0 +1,8 @@ +package <%= migrationVersion %> + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// <%= migrationFunc %> performs in-place store migrations from version <%= fromVersion %> to <%= toVersion %>. +func <%= migrationFunc %>(_ sdk.Context) error { + return nil +} diff --git a/ignite/templates/module/migration/migration.go b/ignite/templates/module/migration/migration.go new file mode 100644 index 0000000000..f648c23f2a --- /dev/null +++ b/ignite/templates/module/migration/migration.go @@ -0,0 +1,40 @@ +package modulemigration + +import ( + "io/fs" + + "github.com/gobuffalo/genny/v2" + "github.com/gobuffalo/plush/v4" + + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/pkg/xgenny" +) + +// NewGenerator returns the generator to scaffold a new module migration. +func NewGenerator(opts *Options) (*genny.Generator, error) { + subFS, err := fs.Sub(files, "files") + if err != nil { + return nil, errors.Errorf("fail to generate sub: %w", err) + } + + g := genny.New() + if err := g.OnlyFS(subFS, nil, nil); err != nil { + return g, err + } + + ctx := plush.NewContext() + ctx.Set("fromVersion", opts.FromVersion) + ctx.Set("migrationFunc", opts.MigrationFunc()) + ctx.Set("migrationName", opts.MigrationName) + ctx.Set("migrationVersion", opts.MigrationVersion()) + ctx.Set("moduleName", opts.ModuleName) + ctx.Set("toVersion", opts.ToVersion) + + g.Transformer(xgenny.Transformer(ctx)) + g.Transformer(genny.Replace("{{migrationName}}", opts.MigrationName.Snake)) + g.Transformer(genny.Replace("{{migrationVersion}}", opts.MigrationVersion())) + g.Transformer(genny.Replace("{{moduleName}}", opts.ModuleName)) + g.RunFn(moduleModify(opts)) + + return g, nil +} diff --git a/ignite/templates/module/migration/module.go b/ignite/templates/module/migration/module.go new file mode 100644 index 0000000000..6f4d545ff3 --- /dev/null +++ b/ignite/templates/module/migration/module.go @@ -0,0 +1,344 @@ +package modulemigration + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "strconv" + + "github.com/gobuffalo/genny/v2" + + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/pkg/xast" +) + +func moduleModify(opts *Options) genny.RunFn { + return func(r *genny.Runner) error { + f, err := r.Disk.Find(opts.ModuleFile()) + if err != nil { + return err + } + + content, err := updateModule(f.String(), opts) + if err != nil { + return err + } + + return r.File(genny.NewFileS(opts.ModuleFile(), content)) + } +} + +func updateModule(content string, opts *Options) (string, error) { + currentVersion, err := ConsensusVersion(content) + if err != nil { + return "", err + } + if currentVersion != opts.FromVersion { + return "", errors.Errorf("expected module consensus version %d, got %d", opts.FromVersion, currentVersion) + } + + content, err = xast.AppendImports( + content, + xast.WithNamedImport(opts.MigrationImportAlias(), opts.MigrationImportPath()), + ) + if err != nil { + return "", err + } + + content, err = setConsensusVersion(content, opts.ToVersion) + if err != nil { + return "", err + } + + return addMigrationRegistration(content, opts) +} + +// ConsensusVersion returns the current module consensus version from module.go content. +func ConsensusVersion(content string) (uint64, error) { + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, "", content, parser.ParseComments) + if err != nil { + return 0, err + } + + expr, err := consensusVersionExpr(file) + if err != nil { + return 0, err + } + + return parseConsensusVersionExpr(file, expr) +} + +func addMigrationRegistration(content string, opts *Options) (string, error) { + info, err := registerServicesInfoFromContent(content) + if err != nil { + return "", err + } + + var functionOptions []xast.FunctionOptions + + if info.needsConfiguratorSetup { + functionOptions = append(functionOptions, xast.AppendFuncCode(configuratorSetupCode(info))) + } + + functionOptions = append(functionOptions, xast.AppendFuncCode(migrationRegistrationCode(info, opts))) + + return xast.ModifyFunction(content, "RegisterServices", functionOptions...) +} + +func configuratorSetupCode(info registerServicesInfo) string { + returnStmt := "return" + if info.returnsError { + returnStmt = "return nil" + } + + return info.cfgVar + ", ok := " + info.parameterName + ".(module.Configurator)\n" + + "if !ok {\n\t" + returnStmt + "\n}" +} + +func migrationRegistrationCode(info registerServicesInfo, opts *Options) string { + handleErr := "panic(err)" + if info.returnsError { + handleErr = "return err" + } + + return "if err := " + info.cfgVar + + ".RegisterMigration(types.ModuleName, " + + strconv.FormatUint(opts.FromVersion, 10) + ", " + + opts.MigrationImportAlias() + "." + opts.MigrationFunc() + + "); err != nil {\n\t" + handleErr + "\n}" +} + +func setConsensusVersion(content string, version uint64) (string, error) { + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, "", content, parser.ParseComments) + if err != nil { + return "", err + } + + commentMap := ast.NewCommentMap(fileSet, file, file.Comments) + + expr, err := consensusVersionExpr(file) + if err != nil { + return "", err + } + + switch versionExpr := expr.(type) { + case *ast.BasicLit: + versionExpr.Value = strconv.FormatUint(version, 10) + case *ast.Ident: + valueSpec, valueIndex, err := findValueSpec(file, versionExpr.Name) + if err != nil { + return "", err + } + valueSpec.Values[valueIndex] = &ast.BasicLit{ + Kind: token.INT, + Value: strconv.FormatUint(version, 10), + } + default: + return "", errors.Errorf("unsupported consensus version expression %T", expr) + } + + file.Comments = commentMap.Filter(file).Comments() + + return formatFile(fileSet, file) +} + +type registerServicesInfo struct { + cfgVar string + needsConfiguratorSetup bool + parameterName string + returnsError bool +} + +func registerServicesInfoFromContent(content string) (registerServicesInfo, error) { + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, "", content, parser.ParseComments) + if err != nil { + return registerServicesInfo{}, err + } + + funcDecl := findFuncDecl(file, "RegisterServices") + if funcDecl == nil { + return registerServicesInfo{}, errors.New("function \"RegisterServices\" not found") + } + if funcDecl.Type.Params == nil || len(funcDecl.Type.Params.List) == 0 || len(funcDecl.Type.Params.List[0].Names) == 0 { + return registerServicesInfo{}, errors.New("RegisterServices must have a named parameter") + } + + param := funcDecl.Type.Params.List[0] + info := registerServicesInfo{ + parameterName: param.Names[0].Name, + returnsError: functionReturnsError(funcDecl), + } + + if isModuleConfiguratorType(param.Type) { + info.cfgVar = info.parameterName + return info, nil + } + + cfgVar := findConfiguratorVar(funcDecl, info.parameterName) + if cfgVar != "" { + info.cfgVar = cfgVar + return info, nil + } + + info.cfgVar = "cfg" + info.needsConfiguratorSetup = true + + return info, nil +} + +func functionReturnsError(funcDecl *ast.FuncDecl) bool { + if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 { + return false + } + + ident, ok := funcDecl.Type.Results.List[0].Type.(*ast.Ident) + return ok && ident.Name == "error" +} + +func findConfiguratorVar(funcDecl *ast.FuncDecl, parameterName string) string { + for _, stmt := range funcDecl.Body.List { + assignStmt, ok := stmt.(*ast.AssignStmt) + if !ok || len(assignStmt.Lhs) < 1 || len(assignStmt.Rhs) != 1 { + continue + } + + typeAssert, ok := assignStmt.Rhs[0].(*ast.TypeAssertExpr) + if !ok || !isModuleConfiguratorType(typeAssert.Type) { + continue + } + + ident, ok := typeAssert.X.(*ast.Ident) + if !ok || ident.Name != parameterName { + continue + } + + cfgVar, ok := assignStmt.Lhs[0].(*ast.Ident) + if !ok { + continue + } + + return cfgVar.Name + } + + return "" +} + +func isModuleConfiguratorType(expr ast.Expr) bool { + switch typedExpr := expr.(type) { + case *ast.Ident: + return typedExpr.Name == "Configurator" + case *ast.SelectorExpr: + return typedExpr.Sel.Name == "Configurator" + default: + return false + } +} + +func consensusVersionExpr(file *ast.File) (ast.Expr, error) { + funcDecl := findFuncDecl(file, "ConsensusVersion") + if funcDecl == nil { + return nil, errors.New("function \"ConsensusVersion\" not found") + } + if funcDecl.Body == nil || len(funcDecl.Body.List) == 0 { + return nil, errors.New("ConsensusVersion has an empty body") + } + + lastStmt, ok := funcDecl.Body.List[len(funcDecl.Body.List)-1].(*ast.ReturnStmt) + if !ok || len(lastStmt.Results) != 1 { + return nil, errors.New("ConsensusVersion must return exactly one value") + } + + return lastStmt.Results[0], nil +} + +func parseConsensusVersionExpr(file *ast.File, expr ast.Expr) (uint64, error) { + switch typedExpr := expr.(type) { + case *ast.BasicLit: + return parseConsensusVersionLiteral(typedExpr) + case *ast.Ident: + valueSpec, valueIndex, err := findValueSpec(file, typedExpr.Name) + if err != nil { + return 0, err + } + return parseConsensusVersionExpr(file, valueSpec.Values[valueIndex]) + default: + return 0, errors.Errorf("unsupported consensus version expression %T", expr) + } +} + +func parseConsensusVersionLiteral(lit *ast.BasicLit) (uint64, error) { + if lit.Kind != token.INT { + return 0, errors.Errorf("unsupported consensus version literal kind %v", lit.Kind) + } + + version, err := strconv.ParseUint(lit.Value, 10, 64) + if err != nil { + return 0, err + } + + return version, nil +} + +func findValueSpec(file *ast.File, name string) (*ast.ValueSpec, int, error) { + for _, decl := range file.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || (genDecl.Tok != token.CONST && genDecl.Tok != token.VAR) { + continue + } + + for _, spec := range genDecl.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + continue + } + + for i, specName := range valueSpec.Names { + if specName.Name != name { + continue + } + if len(valueSpec.Values) == 0 { + return nil, 0, errors.Errorf("%s has no value", name) + } + + valueIndex := i + if valueIndex >= len(valueSpec.Values) { + valueIndex = len(valueSpec.Values) - 1 + } + + return valueSpec, valueIndex, nil + } + } + } + + return nil, 0, errors.Errorf("%s value not found", name) +} + +func findFuncDecl(file *ast.File, name string) *ast.FuncDecl { + for _, decl := range file.Decls { + funcDecl, ok := decl.(*ast.FuncDecl) + if ok && funcDecl.Name.Name == name { + return funcDecl + } + } + + return nil +} + +func formatFile(fileSet *token.FileSet, file *ast.File) (string, error) { + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, file); err != nil { + return "", err + } + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + return "", err + } + + return string(formatted), nil +} diff --git a/ignite/templates/module/migration/module_test.go b/ignite/templates/module/migration/module_test.go new file mode 100644 index 0000000000..45616a6604 --- /dev/null +++ b/ignite/templates/module/migration/module_test.go @@ -0,0 +1,136 @@ +package modulemigration + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v29/ignite/pkg/multiformatname" +) + +func TestUpdateModuleAddsInitialMigration(t *testing.T) { + opts := &Options{ + ModuleName: "blog", + ModulePath: "github.com/test/blog", + MigrationName: multiformatname.MustNewName("initial-state"), + FromVersion: 1, + ToVersion: 2, + } + + got, err := updateModule(moduleWithServiceRegistrar(` +func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { + types.RegisterMsgServer(registrar, keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(registrar, keeper.NewQueryServerImpl(am.keeper)) + + return nil +} + +func (AppModule) ConsensusVersion() uint64 { return 1 } +`), opts) + require.NoError(t, err) + + normalized := normalize(got) + require.Contains(t, normalized, `migrationv2"github.com/test/blog/x/blog/migrations/v2"`) + require.Contains(t, normalized, `cfg,ok:=registrar.(module.Configurator)`) + require.Contains(t, normalized, `if!ok{returnnil}`) + require.Contains(t, normalized, `cfg.RegisterMigration(types.ModuleName,1,migrationv2.MigrateInitialState)`) + require.Contains(t, normalized, `func(AppModule)ConsensusVersion()uint64{return2}`) +} + +func TestUpdateModuleAppendsMigrationToExistingConfiguratorBlock(t *testing.T) { + opts := &Options{ + ModuleName: "blog", + ModulePath: "github.com/test/blog", + MigrationName: multiformatname.MustNewName("add-index"), + FromVersion: 2, + ToVersion: 3, + } + + got, err := updateModule(moduleWithServiceRegistrar(` +func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { + types.RegisterMsgServer(registrar, keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(registrar, keeper.NewQueryServerImpl(am.keeper)) + + cfg, ok := registrar.(module.Configurator) + if !ok { + return nil + } + + if err := cfg.RegisterMigration(types.ModuleName, 1, migrationv2.MigrateInitialState); err != nil { + return err + } + + return nil +} + +func (AppModule) ConsensusVersion() uint64 { return 2 } +`, `migrationv2 "github.com/test/blog/x/blog/migrations/v2"`), opts) + require.NoError(t, err) + + normalized := normalize(got) + require.Equal(t, 1, strings.Count(normalized, `registrar.(module.Configurator)`)) + require.Contains(t, normalized, `migrationv3"github.com/test/blog/x/blog/migrations/v3"`) + require.Contains(t, normalized, `cfg.RegisterMigration(types.ModuleName,1,migrationv2.MigrateInitialState)`) + require.Contains(t, normalized, `cfg.RegisterMigration(types.ModuleName,2,migrationv3.MigrateAddIndex)`) + require.Contains(t, normalized, `func(AppModule)ConsensusVersion()uint64{return3}`) +} + +func TestUpdateModuleSupportsConfiguratorSignatureAndConstantVersion(t *testing.T) { + opts := &Options{ + ModuleName: "blog", + ModulePath: "github.com/test/blog", + MigrationName: multiformatname.MustNewName("normalize-data"), + FromVersion: 2, + ToVersion: 3, + } + + got, err := updateModule(moduleWithConfigurator(` +const ConsensusVersion = 2 + +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) +} + +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } +`), opts) + require.NoError(t, err) + + normalized := normalize(got) + require.NotContains(t, normalized, `registrar.(module.Configurator)`) + require.Contains(t, normalized, `cfg.RegisterMigration(types.ModuleName,2,migrationv3.MigrateNormalizeData)`) + require.Contains(t, normalized, `iferr:=cfg.RegisterMigration(types.ModuleName,2,migrationv3.MigrateNormalizeData);err!=nil{panic(err)}`) + require.Contains(t, normalized, `constConsensusVersion=3`) + + version, err := ConsensusVersion(got) + require.NoError(t, err) + require.EqualValues(t, 3, version) +} + +func moduleWithServiceRegistrar(body string, extraImports ...string) string { + imports := []string{ + `"github.com/cosmos/cosmos-sdk/types/module"`, + `"google.golang.org/grpc"`, + `"github.com/test/blog/x/blog/keeper"`, + `"github.com/test/blog/x/blog/types"`, + } + imports = append(imports, extraImports...) + + return "package blog\n\nimport (\n\t" + strings.Join(imports, "\n\t") + "\n)\n\n" + body +} + +func moduleWithConfigurator(body string, extraImports ...string) string { + imports := []string{ + `"github.com/cosmos/cosmos-sdk/types/module"`, + `"github.com/test/blog/x/blog/keeper"`, + `"github.com/test/blog/x/blog/types"`, + } + imports = append(imports, extraImports...) + + return "package blog\n\nimport (\n\t" + strings.Join(imports, "\n\t") + "\n)\n\n" + body +} + +func normalize(content string) string { + return strings.Join(strings.Fields(content), "") +} diff --git a/ignite/templates/module/migration/options.go b/ignite/templates/module/migration/options.go new file mode 100644 index 0000000000..f4ea228770 --- /dev/null +++ b/ignite/templates/module/migration/options.go @@ -0,0 +1,52 @@ +package modulemigration + +import ( + "fmt" + "path/filepath" + + "github.com/ignite/cli/v29/ignite/pkg/multiformatname" +) + +// Options represents the options to scaffold a module migration. +type Options struct { + ModuleName string + ModulePath string + MigrationName multiformatname.Name + FromVersion uint64 + ToVersion uint64 +} + +// ModuleFile returns the path to the module definition file. +func (opts Options) ModuleFile() string { + return filepath.Join("x", opts.ModuleName, "module", "module.go") +} + +// MigrationVersion returns the migration package name. +func (opts Options) MigrationVersion() string { + return fmt.Sprintf("v%d", opts.ToVersion) +} + +// MigrationDir returns the path to the migration folder. +func (opts Options) MigrationDir() string { + return filepath.Join("x", opts.ModuleName, "migrations", opts.MigrationVersion()) +} + +// MigrationFile returns the path to the migration source file. +func (opts Options) MigrationFile() string { + return filepath.Join(opts.MigrationDir(), fmt.Sprintf("%s.go", opts.MigrationName.Snake)) +} + +// MigrationFunc returns the migration handler function name. +func (opts Options) MigrationFunc() string { + return fmt.Sprintf("Migrate%s", opts.MigrationName.PascalCase) +} + +// MigrationImportAlias returns the import alias used by module.go. +func (opts Options) MigrationImportAlias() string { + return fmt.Sprintf("migrationv%d", opts.ToVersion) +} + +// MigrationImportPath returns the migration import path used by module.go. +func (opts Options) MigrationImportPath() string { + return fmt.Sprintf("%s/x/%s/migrations/%s", opts.ModulePath, opts.ModuleName, opts.MigrationVersion()) +} diff --git a/ignite/templates/module/migration/templates.go b/ignite/templates/module/migration/templates.go new file mode 100644 index 0000000000..cb6a97b4e3 --- /dev/null +++ b/ignite/templates/module/migration/templates.go @@ -0,0 +1,6 @@ +package modulemigration + +import "embed" + +//go:embed files/* files/**/* +var files embed.FS