diff --git a/cmd/cli/cm.go b/cmd/cli/cm.go new file mode 100644 index 0000000..ca8b9d3 --- /dev/null +++ b/cmd/cli/cm.go @@ -0,0 +1,239 @@ +/* +Copyright © 2025 NAME HERE +*/ +package cli + +import ( + "errors" + "fmt" + "os" + "os/exec" + "regexp" + + "github.com/Tomelin/go-convetional-commit/internal/entity" + "github.com/Tomelin/go-convetional-commit/internal/service" + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" +) + +type commitStruct struct{} + +// cmCmd represents the cm command +var cmCmd = &cobra.Command{ + Use: "cm", + Short: "Create a new commit and push", + Long: ` +cli is a tool to create conventional commits +You can create a commit with a message and a body message +You can also create a commit with a task id and a task status +You can also create a commit with an emoji in the message +You can also create a commit with a type of commit +You can also create a commit in interactive mode +You can also push the commit to the remote repository +You can also reset the commit to the head +`, + Run: func(cmd *cobra.Command, args []string) { + + cStruct := commitStruct{} + if err := cStruct.checkGitCommand(); err != nil { + fmt.Println(err) + os.Exit(0) + } + + shortDesc, _ := cmd.Flags().GetBool("short") + + interactive, _ := cmd.Flags().GetBool("interactive") + if interactive { + interactive, err := cStruct.interactiveMode(shortDesc) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + sc := service.NewServiceCommit() + err = sc.Commit(interactive) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + } else { + cStruct.nonInteractiveMode() + } + + push, _ := cmd.Flags().GetBool("push") + if push { + sc := service.NewServiceCommit() + err := sc.Push() + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + } + + reset, _ := cmd.Flags().GetUint("reset") + if reset > 0 { + + sc := service.NewServiceCommit() + + err := sc.Reset(uint(reset)) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + } + }, +} + +func init() { + rootCmd.AddCommand(cmCmd) + cmCmd.Flags().BoolP("push", "p", false, "Push the commit") + cmCmd.Flags().UintP("reset", "r", 0, "Number of head to reaset") + cmCmd.Flags().StringP("subject", "s", "", "Subject message of commit") + cmCmd.Flags().String("type", "", "Commit type (feat, fix, chore, docs, style, refactor, perf, test, ci, build, revert)") + cmCmd.Flags().String("taskId", "", "Task id") + cmCmd.Flags().String("body", "", "Body message of commit") + cmCmd.Flags().String("emoji", "", "Put emoji in commit message") + cmCmd.Flags().BoolP("short", "c", false, "Short description") +} + +func (c *commitStruct) interactiveMode(short bool) (*entity.Commit, error) { + + opts := entity.Commit{} + + // STARTS Commit type + items := opts.Option.OptionsList() + + prompt := promptui.Select{ + Label: "Select kind of commit", + Items: items, + IsVimMode: false, + } + + _, result, err := prompt.Run() + if result == "" { + return nil, errors.New("please select a commit type") + } + + if err != nil { + return nil, fmt.Errorf("select prompt failed %v", err) + } + + opts.Option = opts.Option.FromString(result) + opts.Choice = result + // ENDS Commit type + + // STARTS Commit message + validate := func(input string) error { + if input == "" { + return errors.New("commit message is required") + } + + if len(input) > 40 { + return errors.New("commit message must be at most 40 characters long") + } + + matched, err := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9].{0,39}$`, input) + if err != nil { + return err + } + if !matched { + return errors.New("commit message must start with a letter and be at most 40 characters long") + } + + return nil + } + + prompt2 := promptui.Prompt{ + Label: "Write the commit message", + Validate: validate, + } + + commitMessage, err := prompt2.Run() + if commitMessage == "" { + return nil, errors.New("commit message is required") + } + + if err != nil { + return nil, fmt.Errorf("input prompt failed %v", err) + } + opts.Subject = commitMessage + // ENDS Commit message + + // STARTS body message + commitBody := promptui.Prompt{ + Label: "write a message in the commit body", + } + + commitBodyResult, err := commitBody.Run() + if err != nil { + return nil, fmt.Errorf("input prompt failed %v", err) + } + opts.Comment = commitBodyResult + // ENDS body message + + // Long description + if short { + return &opts, nil + } + + // STARTS task status + promptStatus := promptui.Select{ + Label: "Select task status", + Items: opts.StatusType.StatusList(), + IsVimMode: false, + } + + _, result, err = promptStatus.Run() + if err != nil { + return nil, fmt.Errorf("select prompt failed %v", err) + } + opts.StatusType = opts.StatusType.FromString(result) + // ENDS task status + + // STARTS task id + taskID := promptui.Prompt{ + Label: "Write the task id", + } + + taskIdMessage, err := taskID.Run() + if err != nil { + return nil, fmt.Errorf("input prompt failed %v", err) + } + opts.TaskID = taskIdMessage + // ENDS task id + + // STARTS enable emoji + emoji := promptui.Select{ + Label: "Enable emoji in commit message? (y/n)", + Items: opts.EnableEmoji(), + } + + _, enabledEmoji, err := emoji.Run() + if err != nil { + return nil, fmt.Errorf("input prompt failed %v", err) + } + + if enabledEmoji == "true" { + opts.Emoji = opts.Option.Emoji() + } + // ENDS enable emoji + + if err := opts.Validate(); err != nil { + return nil, err + } + + return &opts, nil +} + +func (c *commitStruct) nonInteractiveMode() { + fmt.Println("Non interactive mode") +} + +func (c *commitStruct) checkGitCommand() error { + if _, err := exec.LookPath("git"); err != nil { + return errors.New("git is not installed") + } + + return nil +} diff --git a/cmd/cli/pr.go b/cmd/cli/pr.go new file mode 100644 index 0000000..91bea68 --- /dev/null +++ b/cmd/cli/pr.go @@ -0,0 +1,178 @@ +/* +Copyright © 2025 NAME HERE +*/ +package cli + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/google/go-github/v57/github" + "github.com/spf13/cobra" + "golang.org/x/oauth2" +) + +type PRStructure struct { + branch string + title string + head string + body string + gitToken string + ownerRepository string + repositoryName string + typeFeature string +} + +// prCmd represents the pr command +var prCmd = &cobra.Command{ + Use: "pr", + Short: "Create a pull request", + Long: `Create a pull request`, + + Run: func(cmd *cobra.Command, args []string) { + + branch, err := cmd.Flags().GetString("destination") + if err != nil { + fmt.Println("Erro ao obter a flag 'branch':", err.Error()) + os.Exit(1) + } + if branch == "" { + fmt.Println("A flag 'branch' é obrigatória") + os.Exit(1) + } + + title, err := cmd.Flags().GetString("title") + if err != nil { + fmt.Println("Erro ao obter a flag 'title':", err.Error()) + os.Exit(1) + } + + if title == "" { + fmt.Println("A flag 'title' é obrigatória") + os.Exit(1) + } + + body, err := cmd.Flags().GetString("body") + if err != nil { + fmt.Println("Erro ao obter a flag 'body':", err.Error()) + os.Exit(1) + } + if body == "" { + fmt.Println("A flag 'body' é obrigatória") + os.Exit(1) + } + + head, err := cmd.Flags().GetString("source") + if err != nil { + fmt.Println("Erro ao obter a flag 'head':", err.Error()) + os.Exit(1) + } + + token, err := cmd.Flags().GetString("token") + if err != nil { + fmt.Println("Erro ao obter a flag 'token':", err.Error()) + os.Exit(1) + } + + owner, err := cmd.Flags().GetString("owner") + if err != nil { + fmt.Println("Erro ao obter a flag 'owner':", err.Error()) + os.Exit(1) + } + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + fmt.Println("Erro ao obter a flag 'repository':", err.Error()) + os.Exit(1) + } + + typeFeature, err := cmd.Flags().GetString("type") + if err != nil { + fmt.Println("Erro ao obter a flag 'repository':", err.Error()) + os.Exit(1) + } + + pr := PRStructure{ + branch: branch, + title: title, + head: head, + body: body, + gitToken: token, + ownerRepository: owner, + repositoryName: repository, + typeFeature: typeFeature, + } + createPR(&pr) + }, +} + +func init() { + + rootCmd.AddCommand(prCmd) + prCmd.Flags().StringP("destination", "d", "", "Destination branch") + prCmd.Flags().StringP("title", "t", "", "PR title") + prCmd.Flags().StringP("source", "s", "", "Source branch") + prCmd.Flags().StringP("body", "b", "", "Create body") + prCmd.Flags().String("type", "f", "Commit type (feat, fix, chore, docs, style, refactor, perf, test, ci, build, revert)") + prCmd.Flags().StringP("token", "e", "", "Git Token Environment variable default (GIT_TOKEN)") + prCmd.Flags().StringP("owner", "o", "", "Git owner repository Environment variable default (GIT_OWNER)") + prCmd.Flags().String("repository", "r", "Git repository name") + + prCmd.MarkFlagRequired("repository") + prCmd.MarkFlagRequired("type") +} + +func createPR(prData *PRStructure) { + + token := os.Getenv("GIT_TOKEN") + if prData.gitToken != "" { + token = prData.gitToken + } + if token == "" { + log.Fatal("A variável de ambiente GITHUB_TOKEN não está definida.") + } + + repoOwner := os.Getenv("GIT_OWNER") + if prData.ownerRepository != "" { + repoOwner = prData.ownerRepository + } + if repoOwner == "" { + log.Fatal("A variável de ambiente GIT_OWNER não está definida.") + } + + repoName := os.Getenv("GIT_REPO") + if prData.repositoryName == "" && repoName == "" { + log.Fatal("repository name is required") + } + + if prData.repositoryName != "" { + repoName = prData.repositoryName + } + + ctx := context.Background() + + // Autenticação + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + title := fmt.Sprintf("%s: %s", prData.typeFeature, prData.title) + newPR := &github.NewPullRequest{ + Title: github.String(title), + Head: github.String(prData.head), + Base: github.String(prData.branch), + Body: github.String(prData.body), + MaintainerCanModify: github.Bool(true), // Permite que o mantenedor modifique o PR + } + + pr, _, err := client.PullRequests.Create(ctx, repoOwner, prData.repositoryName, newPR) + if err != nil { + log.Fatalf("Erro ao criar o Pull Request: %v", err) + } + + fmt.Printf("Pull Request criado com sucesso! URL: %s\n", pr.GetHTMLURL()) +} diff --git a/cmd/cli/root.go b/cmd/cli/root.go index 64e4444..87df162 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -4,80 +4,16 @@ Copyright © 2024 Rafael Tomelin package cli import ( - "errors" - "fmt" "os" - "os/exec" - "regexp" - "github.com/Tomelin/go-convetional-commit/internal/entity" - "github.com/Tomelin/go-convetional-commit/internal/service" - "github.com/manifoldco/promptui" "github.com/spf13/cobra" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "cli", - Short: "Create a conventional commit", - Long: ` -cli is a tool to create conventional commits -You can create a commit with a message and a body message -You can also create a commit with a task id and a task status -You can also create a commit with an emoji in the message -You can also create a commit with a type of commit -You can also create a commit in interactive mode -You can also push the commit to the remote repository -You can also reset the commit to the head -`, - - Run: func(cmd *cobra.Command, args []string) { - if err := checkGitCommand(); err != nil { - fmt.Println(err) - os.Exit(0) - } - - interactive, _ := cmd.Flags().GetBool("interactive") - if interactive { - interactive, err := interactiveMode() - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - - sc := service.NewServiceCommit() - err = sc.Commit(interactive) - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - - } else { - nonInteractiveMode() - } - - push, _ := cmd.Flags().GetBool("push") - if push { - sc := service.NewServiceCommit() - err := sc.Push() - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - } - - reset, _ := cmd.Flags().GetUint("reset") - if reset > 0 { - - sc := service.NewServiceCommit() - - err := sc.Reset(uint(reset)) - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - } - }, + Use: "gCommit", + Short: "Work git with convetional commit", + Long: "Work git with convetional commit", } // Execute adds all child commands to the root command and sets flags appropriately. @@ -92,155 +28,4 @@ func Execute() { func init() { rootCmd.PersistentFlags().BoolP("interactive", "i", false, "Interactive mode") - rootCmd.Flags().BoolP("push", "p", false, "Push the commit") - rootCmd.Flags().UintP("reset", "r", 0, "Number of head to reaset") - rootCmd.Flags().StringP("subject", "s", "", "Subject message of commit") - rootCmd.Flags().String("type", "", "Commit type (feat, fix, chore, docs, style, refactor, perf, test, ci, build, revert)") - rootCmd.Flags().String("taskId", "", "Task id") - rootCmd.Flags().String("body", "", "Body message of commit") - rootCmd.Flags().String("emoji", "", "Put emoji in commit message") -} - -// interactiveMode is a function that runs the interactive mode -// It uses the promptui library to create a prompt -// It returns an error if the prompt fails -// Exmaple of commit: -// -// feat: :sparkles: add new feature -// -// message in the body -// Resolve: #123 -func interactiveMode() (*entity.Commit, error) { - - opts := entity.Commit{} - - // STARTS Commit type - items := opts.Option.OptionsList() - - prompt := promptui.Select{ - Label: "Select kind of commit", - Items: items, - IsVimMode: false, - } - - _, result, err := prompt.Run() - if result == "" { - return nil, errors.New("please select a commit type") - } - - if err != nil { - return nil, fmt.Errorf("select prompt failed %v", err) - } - opts.Option = opts.Option.FromString(result) - opts.Choice = result - // ENDS Commit type - - // STARTS Commit message - validate := func(input string) error { - if input == "" { - return errors.New("commit message is required") - } - - if len(input) > 40 { - return errors.New("commit message must be at most 40 characters long") - } - - matched, err := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9].{0,39}$`, input) - if err != nil { - return err - } - if !matched { - return errors.New("commit message must start with a letter and be at most 40 characters long") - } - - return nil - } - - prompt2 := promptui.Prompt{ - Label: "Write the commit message", - Validate: validate, - } - - commitMessage, err := prompt2.Run() - if commitMessage == "" { - return nil, errors.New("commit message is required") - } - - if err != nil { - return nil, fmt.Errorf("input prompt failed %v", err) - } - opts.Subject = commitMessage - // ENDS Commit message - - // STARTS body message - commitBody := promptui.Prompt{ - Label: "write a message in the commit body", - } - - commitBodyResult, err := commitBody.Run() - if err != nil { - return nil, fmt.Errorf("input prompt failed %v", err) - } - opts.Comment = commitBodyResult - // ENDS body message - - // STARTS task status - promptStatus := promptui.Select{ - Label: "Select task status", - Items: opts.StatusType.StatusList(), - IsVimMode: false, - } - - _, result, err = promptStatus.Run() - if err != nil { - return nil, fmt.Errorf("select prompt failed %v", err) - } - opts.StatusType = opts.StatusType.FromString(result) - // ENDS task status - - // STARTS task id - taskID := promptui.Prompt{ - Label: "Write the task id", - } - - taskIdMessage, err := taskID.Run() - if err != nil { - return nil, fmt.Errorf("input prompt failed %v", err) - } - opts.TaskID = taskIdMessage - // ENDS task id - - // STARTS enable emoji - emoji := promptui.Select{ - Label: "Enable emoji in commit message? (y/n)", - Items: opts.EnableEmoji(), - } - - _, enabledEmoji, err := emoji.Run() - if err != nil { - return nil, fmt.Errorf("input prompt failed %v", err) - } - - if enabledEmoji == "true" { - opts.Emoji = opts.Option.Emoji() - } - // ENDS enable emoji - - if err := opts.Validate(); err != nil { - return nil, err - } - - return &opts, nil -} - -func nonInteractiveMode() { - fmt.Println("Non interactive mode") -} - -func checkGitCommand() error { - if _, err := exec.LookPath("git"); err != nil { - return errors.New("git is not installed") - } - - return nil } diff --git a/go.mod b/go.mod index 8cbef19..3902362 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,15 @@ module github.com/Tomelin/go-convetional-commit go 1.23.1 require ( + github.com/google/go-github/v57 v57.0.0 github.com/manifoldco/promptui v0.9.0 github.com/spf13/cobra v1.8.1 + golang.org/x/oauth2 v0.30.0 ) require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect diff --git a/go.sum b/go.sum index fe58bc3..bf89104 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,13 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs= +github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= @@ -14,7 +21,10 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=