diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 135cce1..037ccf6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -63,10 +63,10 @@ - + - + diff --git a/README.md b/README.md index 7cef284..1074c31 100644 --- a/README.md +++ b/README.md @@ -8,25 +8,71 @@ - Built-in helper scripts (build.sh in some projects). - Self-learning helper comments (Detailed instructions to customize the files, like instructions to controllers, views, models) +### Installing + +To install you can use npm to get globally on your machine: + +`npm i -g recipe-cli` + +After this you can get the list of project typing `recipe-cli` or init some project with `recipe-cli {language}`, like: + +`recipe-cli golang` + +A bunch of options will be showed to configure your project. + ## Golang Projects Boilerplates -**API Structure** +**1. API Project Structure (Mux Router)** + +- Two database pre-configured configurations and models (Firebase || MongoDB) || Empty structure. +- Heroku configuration files and build scripts. +- MVP Project Structure with routes example, ready to customize. +- Utilities package for JSON responses and Token validation already created. +- Pre-configured Github Action script for deploy on heroku. +- Pre-configured CORS. ``` project - ├── api - │ ├── controllers - │ │ └── entity.go (controller example, customize your own from here) - │ ├── db - │ │ └── database.go (two options pre-configured - Firebase / MongoDB | also can be boilerplated with empty if no database choosed) - │ ├── models - │ │ ├── Entity.go (depends of the database selected) - │ │ └── Token.go - │ ├── repository - │ │ └── entity_repository - │ ├── responses - │ │ └── responses.go - │ └── server.go - ├── go.mod - └── main.go -``` \ No newline at end of file +├── Procfile +├── api +│ ├── controllers +│ │ └── entity.go (controller used on entity_routes inside routes package) +│ ├── db +│ │ └── database.go (database connection - Firebase || MongoDB || Empty) +│ ├── middlewares +│ │ └── middlewares.go (middleware functions to deal with cors configuration) +│ ├── models +│ │ ├── Entity.go (example of model in accord with database choosed) +│ │ └── Token.go (token model to be used with token validation function in security package) +│ ├── repository +│ │ └── entity_repository (example of repository function used inside controllers) +│ ├── responses +│ │ └── responses.go (utility to format JSON responses from the API) +│ ├── router +│ │ ├── router.go +│ │ └── routes (route pre-configured for each controller) +│ ├── server.go +│ └── utils +│ ├── json_utility (utility to work with Structs -> JSON management) +│ └── security (utility for validate token) +├── build.sh +├── config +│ └── config.go +├── deploy_to_heroku.sh (deploy to heroku with sh deploy_to_heroku.sh file) +├── go.mod +├── go.sum +├── heroku.yml (heroku configuration file for go projects) +├── main.go +└── vendor (vendoring of the dependencies) + ... + +30 directories, 17 files +``` + +**2. CLI Project Structure** + +- Utilities for command-line interface, like **selectors** and input user commands with validation. +- Utility to integrate shell commands inside your application. +- Pre-configured release configuration and script. +- CI CD Scripts for publish release pre-configured. +- NPM deploy script configured, production-ready. diff --git a/cmd/golang/golang.go b/cmd/golang/golang.go index a722a5f..9244e84 100644 --- a/cmd/golang/golang.go +++ b/cmd/golang/golang.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/jsdaniell/recipe-cli/cli" "github.com/jsdaniell/recipe-cli/cmd/golang/golang_api" + "github.com/jsdaniell/recipe-cli/cmd/golang/golang_cli" "github.com/spf13/cobra" "log" ) @@ -30,31 +31,35 @@ const ( // GoCmd handles all the golang project types. var GoCmd = &cobra.Command{ Use: "golang", - Short: "Choose the type of project", - Long: `All software has versions. This is Hugo's`, + Short: "Choose the type of project:", + Long: `Different golang projects options for boilerplate.`, Run: func(cmd *cobra.Command, args []string) { projectType, err := cli.SelectorCli("Choose the type of golang project:", API, CLI) if err != nil { log.Fatal(err) } - projectDatabase, err := cli.SelectorCli("Choose the database used on project:", MongoDBDatabase, FirebaseDatabase, NoSelection) + projectName, err := cli.UserInput("Type the name of the project", validateProjectName) if err != nil { log.Fatal(err) } - projectName, err := cli.UserInput("Type the name of the project" ,validateProjectName) - if err != nil { - log.Fatal(err) - } - - username, err := cli.UserInput("Type your username, naturally the package of go api will be (github.com/username/project_name):" ,validateProjectName) + username, err := cli.UserInput("Type your username, naturally the package of go api will be (github.com/username/project_name):", validateProjectName) if err != nil { log.Fatal(err) } if projectType == API { + projectDatabase, err := cli.SelectorCli("Choose the database used on project:", MongoDBDatabase, FirebaseDatabase, NoSelection) + if err != nil { + log.Fatal(err) + } + golang_api.InitRoot(username, projectName, projectDatabase) } + + if projectType == CLI { + golang_cli.InitRoot(username, projectName) + } }, } \ No newline at end of file diff --git a/cmd/golang/golang_cli/golang_cli_content_files/ci_github_files.go b/cmd/golang/golang_cli/golang_cli_content_files/ci_github_files.go new file mode 100644 index 0000000..580ac31 --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/ci_github_files.go @@ -0,0 +1,64 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateCIGithubFiles(projectName string) { + err := os.Mkdir(projectName + "/.github", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + err = os.Mkdir(projectName + "/.github/workflows", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + writeGithubWorkflowFile(projectName) + + +} + +func writeGithubWorkflowFile(projectName string){ + var content = `name: goreleaser + +on: + push: + tags: + - '*' + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.TOKEN_GO_RELEASE }} +` + + file, err := os.Create(projectName + "/.github/workflows/release.yml") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/golang/golang_cli/golang_cli_content_files/cli_files.go b/cmd/golang/golang_cli/golang_cli_content_files/cli_files.go new file mode 100644 index 0000000..eb0636d --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/cli_files.go @@ -0,0 +1,115 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateCLIPackage(projectName string) { + err := os.Mkdir(projectName + "/cli", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + writeCLISelectorFile(projectName) + writeUserInputFile(projectName) + + +} + +func writeCLISelectorFile(projectName string){ + var content = `package cli + +import ( + "github.com/manifoldco/promptui" + "log" +) + +// SelectorCli helps to get user input showing a selector with the passed options and label. +func SelectorCli(label string, options ...string) (string, error) { + s := promptui.Select{ + Label: label, + Items: options, + } + + _, result, err := s.Run() + if err != nil { + log.Fatal(err) + } + + return result, nil +} +` + + file, err := os.Create(projectName + "/cli/selector_cli.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} + +func writeUserInputFile(projectName string){ + var content = `package cli + +import ( + "errors" + "fmt" + "github.com/manifoldco/promptui" +) + +// An example of validate function: +// +// validate := func(input string) error { +// _, err := strconv.ParseFloat(input, 64) +// if err != nil { +// return errors.New("Invalid number") +// } +// return nil +// } + +// UserInput allow to get the user input with optional validate function. +func UserInput(label string, validate ...promptui.ValidateFunc) (string, error){ + + var validation promptui.ValidateFunc + + if len(validate) == 0 { + validation = promptui.ValidateFunc(func(s string) error { + return nil + }) + } else if len(validate) == 1 { + validation = validate[0] + } else if len(validate) > 1 { + return "", errors.New("it's permited only one validation function parameter.") + } + + prompt := promptui.Prompt{ + Label: label, + Validate: validation, + } + + result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return "", err + } + + return result, nil +} + +` + + file, err := os.Create(projectName + "/cli/user_input.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/golang/golang_cli/golang_cli_content_files/cmd_files.go b/cmd/golang/golang_cli/golang_cli_content_files/cmd_files.go new file mode 100644 index 0000000..1b30ff3 --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/cmd_files.go @@ -0,0 +1,98 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateCmdPackage(username, projectName string) { + err := os.Mkdir(projectName + "/cmd", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + writeApiServerFile(username, projectName) +} + +func writeApiServerFile(username, projectName string){ + var content = `package cmd + +import ( + "fmt" + "github.com/` + username + `/` + projectName + `/cmd/other_command" + "github.com/spf13/cobra" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +) + +var cfgFile string + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "` + projectName + `", + Short: "This is a simple cli tool to generate different boilerplate for different type of projects.", + Long: ` + "`" + `The original idea comes from the necessity of create different projects with different newer frameworks that's + coming from the newer technologies.` + "`" + `, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(` + "`" + `Welcome to your CLI application!` + "`" + `) + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + cobra.CheckErr(RootCmd.Execute()) +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.recipe-cli.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + RootCmd.AddCommand(golang.OtherSubCommand) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + cobra.CheckErr(err) + + // Search config in home directory with name ".recipe-cli" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".recipe-cli") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} +` + + file, err := os.Create(projectName + "/cmd/root.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/golang/golang_cli/golang_cli_content_files/other_command_files.go b/cmd/golang/golang_cli/golang_cli_content_files/other_command_files.go new file mode 100644 index 0000000..40bfcbe --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/other_command_files.go @@ -0,0 +1,57 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateOtherCommandPackage(username, projectName string) { + err := os.Mkdir(projectName + "/cmd/other_command", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + writeOtherCommandFile(username, projectName) +} + +func writeOtherCommandFile(username, projectName string){ + var content = `package golang + +import ( + "errors" + "github.com/` + username + `/` + projectName + `/cli" + "github.com/spf13/cobra" +) + + func validateProjectName(input string) error { + if len(input) < 3 { + return errors.New("you have to type a project name") + } + + // TODO: Test if contains no recommended characters like - * ( ) and others. + + return nil + } + + +// OtherCommand handles other command, customize it!. +var OtherCommand = &cobra.Command{ + Use: "othercommand", + Short: "Other command short description", + Long: ` + "`" + `Other command long description` + "`" + `, + Run: func(cmd *cobra.Command, args []string) { + // run your different command + }, +} +` + + file, err := os.Create(projectName + "/cmd/other_command/other_command.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/golang/golang_cli/golang_cli_content_files/root_files.go b/cmd/golang/golang_cli/golang_cli_content_files/root_files.go new file mode 100644 index 0000000..03e6d40 --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/root_files.go @@ -0,0 +1,108 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateMainFile(username, projectName string) { + var content = `package main + +import ( + "github.com/`+ username +`/`+ projectName +`/cmd" +) + +func main() { + cmd.Execute() +}` + + file, err := os.Create(projectName + "/main.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} + +func CreateGoReleaseFile(projectName string) { + var content = `# This is an example .goreleaser.yml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... +project_name: `+ projectName +` +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' +` + + file, err := os.Create(projectName + "/.goreleaser.yml") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} + +func CreatePackageJSONFile(username, projectName string) { + var content = `{ + "name": "` + projectName + `", + "version": "1.0.0", + "description": "Description of your project", + "main": "index.js", + "scripts": { + "postinstall": "go-npm install", + "preuninstall": "go-npm uninstall" + }, + "goBinary": { + "name": "` + projectName + `", + "path": "./bin", + "url": "https://github.com/` + username + `/` + projectName + `/releases/download/v{{version}}/recipe-cli_{{version}}_{{platform}}_{{arch}}.tar.gz" + }, + "author": "` + username + `", + "license": "ISC", + "bugs": { + "url": "https://github.com/` + username + `/` + projectName + `/issues" + }, + "homepage": "https://github.com/` + username + `/` + projectName + `#readme", + "dependencies": { + "go-npm": "^0.1.9", + "recipe-cli": "latest" + } +} + +` + + file, err := os.Create(projectName + "/package.json") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} diff --git a/cmd/golang/golang_cli/golang_cli_content_files/utils_files.go b/cmd/golang/golang_cli/golang_cli_content_files/utils_files.go new file mode 100644 index 0000000..46cb4f7 --- /dev/null +++ b/cmd/golang/golang_cli/golang_cli_content_files/utils_files.go @@ -0,0 +1,160 @@ +package golang_cli_content_files + +import ( + "log" + "os" +) + +func CreateUtilsPackage(projectName string) { + err := os.Mkdir(projectName + "/utils", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + err = os.Mkdir(projectName + "/utils/go_commands", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + err = os.Mkdir(projectName + "/utils/shell_commands", os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + writeGoMod(projectName) + writeShellCommands(projectName) +} + +func writeGoMod(projectName string){ + var content = `package go_commands + +import ( + "fmt" + execute "github.com/alexellis/go-execute/pkg/v1" +) + +func GoModInit(username, projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"mod", "init", "github.com/" + username + "/" + projectName}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} + +func GoModTidy(projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"mod", "tidy"}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} + +func GoGet(packageName, projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"get", "-u", packageName}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} + +func GoModVendor(projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"mod", "vendor"}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} +` + + file, err := os.Create(projectName + "/utils/go_commands/go_mod.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} + +func writeShellCommands(projectName string){ + var content = `package shell_commands + +import ( + "fmt" + execute "github.com/alexellis/go-execute/pkg/v1" +) + +func ExecuteSh(file, projectName string){ + cmd := execute.ExecTask{ + Command: "sh", + Args: []string{file}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} + +func ExecuteShellCommand(command string, projectName string, args ...string){ + cmd := execute.ExecTask{ + Command: command, + Args: args, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} +` + + file, err := os.Create(projectName + "/utils/go_commands/shell_commands.go") + if err != nil { + log.Fatal(err) + } + + _, err = file.WriteString(content) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/golang/golang_cli/init_root.go b/cmd/golang/golang_cli/init_root.go new file mode 100644 index 0000000..54e3bcd --- /dev/null +++ b/cmd/golang/golang_cli/init_root.go @@ -0,0 +1,31 @@ +package golang_cli + +import ( + "github.com/jsdaniell/recipe-cli/cmd/golang/golang_cli/golang_cli_content_files" + "github.com/jsdaniell/recipe-cli/utils/go_commands" + "log" + "os" +) + +func InitRoot(username, projectName string){ + err := os.Mkdir(projectName, os.FileMode(0777)) + if err != nil { + log.Fatal(err) + } + + golang_cli_content_files.CreateMainFile(username, projectName) + + go_commands.GoModInit(username, projectName) + go_commands.GoGet("github.com/spf13/cobra", projectName) + + golang_cli_content_files.CreateGoReleaseFile(projectName) + golang_cli_content_files.CreatePackageJSONFile(username, projectName) + golang_cli_content_files.CreateCIGithubFiles(projectName) + golang_cli_content_files.CreateCmdPackage(username, projectName) + golang_cli_content_files.CreateOtherCommandPackage(username, projectName) + golang_cli_content_files.CreateCLIPackage(projectName) + golang_cli_content_files.CreateUtilsPackage(projectName) + + go_commands.GoModTidy(projectName) + go_commands.GoModVendor(projectName) +} \ No newline at end of file diff --git a/package.json b/package.json index 3709aa6..c0cd89d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "recipe-cli", - "version": "1.0.7", + "version": "1.0.8", "description": "Easy cli solution to generate boilerplate projects", "main": "index.js", "scripts": { @@ -20,6 +20,6 @@ "homepage": "https://github.com/jsdaniell/recipe-cli#readme", "dependencies": { "go-npm": "^0.1.9", - "recipe-cli": "^1.0.5" + "recipe-cli": "latest" } } diff --git a/utils/go_commands/go_mod.go b/utils/go_commands/go_mod.go index 709c15f..f5d89be 100644 --- a/utils/go_commands/go_mod.go +++ b/utils/go_commands/go_mod.go @@ -36,3 +36,35 @@ func GoModTidy(projectName string){ fmt.Printf("output: %s", res.Stderr) } + +func GoGet(packageName, projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"get", "-u", packageName}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +} + +func GoModVendor(projectName string){ + cmd := execute.ExecTask{ + Command: "go", + Args: []string{"mod", "vendor"}, + StreamStdio: false, + Cwd: projectName, + } + + res, err := cmd.Execute() + if err != nil { + panic(err) + } + + fmt.Printf("output: %s", res.Stderr) +}