diff --git a/cmd/spawn/main.go b/cmd/spawn/main.go index 0decbe65..5729776e 100644 --- a/cmd/spawn/main.go +++ b/cmd/spawn/main.go @@ -22,6 +22,7 @@ func main() { rootCmd.AddCommand(newChain) rootCmd.AddCommand(LocalICCmd) rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(moduleCmd) rootCmd.PersistentFlags().String(LogLevelFlag, "info", "log level (debug, info, warn, error)") diff --git a/cmd/spawn/module.go b/cmd/spawn/module.go new file mode 100644 index 00000000..acddb801 --- /dev/null +++ b/cmd/spawn/module.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "io/fs" + "log/slog" + "os" + "path" + "strings" + + "github.com/spf13/cobra" + "github.com/strangelove-ventures/simapp" + "gitub.com/strangelove-ventures/spawn/spawn" +) + +var moduleCmd = &cobra.Command{ + Use: "module [name]", + Short: "Create a new module scaffolding", + Example: `spawn module mymodule`, + Args: cobra.ExactArgs(1), + Aliases: []string{"m", "mod", "proto"}, + Run: func(cmd *cobra.Command, args []string) { + + // TODO: Are special characters allowed? + // ext name is the x/ 'module' name. + extName := strings.ToLower(args[0]) + + // TODO: `module name` will regen if it does not already exist, + // else it will add in a base template. (Smart create/edit) + + // if does not exist: + SetupModule(GetLogger(), extName) + // else: + // make proto-gen for the user / refresh? + + // Announce the new module & how to code gen + fmt.Printf("\n🎉 New Module '%s' generated!\n", extName) + fmt.Println("🏅Generate Go Code:") + fmt.Println(" - $ make proto-gen # convert proto -> code and depinject") + }, +} + +func SetupModule(logger *slog.Logger, extName string) error { + protoFS := simapp.ProtoModuleFS + + if err := os.MkdirAll("proto", 0755); err != nil { + panic(err) + } + + cwd, err := os.Getwd() + if err != nil { + fmt.Println("Error getting current working directory", err) + return err + } + + moduleName := readCurrentModuleName(path.Join(cwd, "go.mod")) + moduleNameProto := convertModuleNameToProto(moduleName) + + return fs.WalkDir(protoFS, ".", func(relPath string, d fs.DirEntry, e error) error { + newPath := path.Join(cwd, relPath) + fc, err := spawn.GetFileContent(logger, newPath, protoFS, relPath, d) + if err != nil { + return err + } else if fc == nil { + return nil + } + + // rename proto path for the new module + exampleProtoPath := path.Join("proto", "example") + if fc.ContainsPath(exampleProtoPath) { + newBinPath := path.Join("proto", extName) + fc.NewPath = strings.ReplaceAll(fc.NewPath, exampleProtoPath, newBinPath) + } + + // any file content that has github.com/strangelove-ventures/simapp replace to moduleName + fc.ReplaceAll("github.com/strangelove-ventures/simapp", moduleName) + fc.ReplaceAll("strangelove_ventures.simapp", moduleNameProto) + + // TODO: maybe juts a straight up replace all on 'example' here instead? + fc.ReplaceAll("x/example", fmt.Sprintf("x/%s", extName)) + fc.ReplaceAll("example/Params", fmt.Sprintf("%s/Params", extName)) + fc.ReplaceAll("example/v1/params", fmt.Sprintf("%s/v1/params", extName)) + fc.ReplaceAll("package example.v1", fmt.Sprintf("package %s.v1", extName)) + fc.ReplaceAll(`import "example`, fmt.Sprintf(`import "%s`, extName)) + + // TODO: set the values in the keepers / msg server automatically + + return fc.Save() + }) +} + +// readCurrentModuleName reads the module name from the go.mod file on the host machine. +func readCurrentModuleName(loc string) string { + if !strings.HasSuffix(loc, "go.mod") { + loc = path.Join(loc, "go.mod") + } + + // read file from path into a []byte + var fileContent []byte + fileContent, err := os.ReadFile(loc) + if err != nil { + fmt.Println("Error reading file", err) + return "" + } + + lines := strings.Split(string(fileContent), "\n") + for _, line := range lines { + if strings.Contains(line, "module") { + return strings.Split(line, " ")[1] + } + } + + return "" +} + +func convertModuleNameToProto(moduleName string) string { + // github.com/rollchains/myproject -> rollchains.myproject + text := strings.Replace(moduleName, "github.com/", "", 1) + return strings.Replace(text, "/", ".", -1) +} diff --git a/simapp/embed.go b/simapp/embed.go index 64514da8..2dbeab77 100644 --- a/simapp/embed.go +++ b/simapp/embed.go @@ -17,4 +17,4 @@ var ICTestFS embed.FS // We only need to copy over the proto/ here, since the x/ will be generated automatically // //go:embed proto/* -var ProtoModule embed.FS +var ProtoModuleFS embed.FS diff --git a/simapp/proto/example/v1/genesis.proto b/simapp/proto/example/v1/genesis.proto index 9b7f779f..a156aca6 100644 --- a/simapp/proto/example/v1/genesis.proto +++ b/simapp/proto/example/v1/genesis.proto @@ -14,7 +14,7 @@ message GenesisState { // Params defines the set of packetforward parameters. message Params { - option (amino.name) = "poa/params"; + option (amino.name) = "example/params"; option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; diff --git a/simapp/proto/example/v1/query.proto b/simapp/proto/example/v1/query.proto index f70a0066..04febf7b 100644 --- a/simapp/proto/example/v1/query.proto +++ b/simapp/proto/example/v1/query.proto @@ -10,7 +10,7 @@ option go_package = "github.com/strangelove-ventures/simapp/x/example/types"; service Query { // Params queries all parameters of the packetforward module. rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/edxample/v1/params"; + option (google.api.http).get = "/example/v1/params"; } } diff --git a/simapp/scripts/protocgen.sh b/simapp/scripts/protocgen.sh index 748614f7..18e5e3da 100644 --- a/simapp/scripts/protocgen.sh +++ b/simapp/scripts/protocgen.sh @@ -22,6 +22,16 @@ buf generate --template buf.gen.pulsar.yaml cd .. mv github.com/strangelove-ventures/simapp/* ./ +rm -rf github.com + +# Copy files over for dep injection rm -rf api && mkdir api -mv example/* ./api -rm -rf github.com example +custom_modules=$(find . -name 'module' -type d -not -path "./proto/*") +for module in $custom_modules; do + dirPath=`basename $(dirname $module)` + mkdir -p api/$dirPath + + mv $dirPath/* ./api/$dirPath/ + rm -rf $dirPath +done + diff --git a/spawn/chain_config.go b/spawn/chain_config.go index 5257a31f..9e058a19 100644 --- a/spawn/chain_config.go +++ b/spawn/chain_config.go @@ -98,7 +98,7 @@ func (cfg *NewChainConfig) SetupMainChainApp() error { simappFS := simapp.SimAppFS return fs.WalkDir(simappFS, ".", func(relPath string, d fs.DirEntry, e error) error { newPath := path.Join(newDirName, relPath) - fc, err := cfg.getFileContent(newPath, simappFS, relPath, d) + fc, err := GetFileContent(cfg.Logger, newPath, simappFS, relPath, d) if err != nil { return err } else if fc == nil { @@ -142,7 +142,7 @@ func (cfg *NewChainConfig) SetupInterchainTest() error { newPath = strings.ReplaceAll(newPath, "go.mod_", "go.mod") } - fc, err := cfg.getFileContent(newPath, ictestFS, relPath, d) + fc, err := GetFileContent(cfg.Logger, newPath, ictestFS, relPath, d) if err != nil { return err } else if fc == nil { @@ -170,7 +170,7 @@ func (cfg *NewChainConfig) SetupInterchainTest() error { }) } -func (cfg *NewChainConfig) getFileContent(newFilePath string, fs embed.FS, relPath string, d fs.DirEntry) (*FileContent, error) { +func GetFileContent(logger *slog.Logger, newFilePath string, fs embed.FS, relPath string, d fs.DirEntry) (*FileContent, error) { if relPath == "." { return nil, nil } @@ -179,10 +179,10 @@ func (cfg *NewChainConfig) getFileContent(newFilePath string, fs embed.FS, relPa return nil, nil } - fc := NewFileContent(cfg.Logger, relPath, newFilePath) + fc := NewFileContent(logger, relPath, newFilePath) if fc.HasIgnoreFile() { - cfg.Logger.Debug("Ignoring file", "file", fc.NewPath) + logger.Debug("Ignoring file", "file", fc.NewPath) return nil, nil }