From dddbe77ef33a815fecf3f5b0e8fb713e1e2f092c Mon Sep 17 00:00:00 2001 From: Ashwin Gopalsamy <47941624+ashwingopalsamy@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:18:18 +0530 Subject: [PATCH 1/2] docs: add guide for fixing PATH-related issues after installation --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/README.md b/README.md index c7b8063..0a5f150 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ commitlint checks if your commit message meets the [conventional commit format]( - [Commit Types](#commit-types) - [Available Rules](#available-rules) - [Available Formatters](#available-formatters) + - [Common Installation Issues](#common-installation-issues) - [Extensibility](#extensibility) - [FAQ](#faq) - [License](#license) @@ -247,6 +248,60 @@ Total 1 errors, 0 warnings, 0 other severities {"input":"fear: do not fear for commit message","issues":[{"description":"type 'fear' is not allowed, you can use one of [build chore ci docs feat fix perf refactor revert style test]","name":"type-enum","severity":"error"}]} ``` +## Common Installation Issues + +If you encounter the `command not found: commitlint` error after installing `commitlint`, this likely means the binary is not in your system's `PATH`. Follow these steps to resolve this issue: + +1. **Ensure `GOPATH/bin` is in your `PATH`**: + By default, Go installs binaries to `$GOPATH/bin` or `$HOME/go/bin` if `GOPATH` is not set. Add this directory to your `PATH`: + + ```bash + export PATH=$PATH:$GOPATH/bin + ``` + + Or, if `GOPATH` is not set: + + ```bash + export PATH=$PATH:$HOME/go/bin + ``` + + Add this line to your shell configuration file (e.g., `~/.bashrc`, `~/.zshrc`) to make the change permanent: + + ```bash + echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.zshrc + source ~/.zshrc + ``` + +2. **Verify the Installation**: + Confirm that the `commitlint` binary is in the expected directory: + + ```bash + ls $GOPATH/bin/commitlint + ``` + + Or: + + ```bash + ls $HOME/go/bin/commitlint + ``` + +3. **Reinstall if Necessary**: + If the binary is missing, reinstall `commitlint`: + + ```bash + go install github.com/conventionalcommit/commitlint@latest + ``` + +4. **Post-Verification**: + After following these steps, try running `commitlint` again: + + ```bash + commitlint init --global + ``` + +This should allow `commitlint` to be recognized as a command in your terminal. + + ## Extensibility ## FAQ From 9118ea84eba4262747ba2190612f68340cd7d9a4 Mon Sep 17 00:00:00 2001 From: Ashwin Gopalsamy Date: Mon, 4 Nov 2024 09:04:54 +0530 Subject: [PATCH 2/2] refactor: unify error handling in internal/cmd Added `handleError` utility function to standardize error handling --- go.mod | 10 +++++----- go.sum | 8 ++++++++ internal/cmd/cli.go | 19 ++++++++++--------- internal/cmd/cmd.go | 3 ++- internal/cmd/config.go | 13 +++++++------ internal/cmd/debug.go | 12 ++++++------ internal/cmd/error.go | 17 +++++++++++++++++ internal/cmd/hook.go | 30 +++++++++++++++++------------- internal/cmd/init.go | 8 ++++---- internal/cmd/lint.go | 34 +++++++++++++++++----------------- 10 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 internal/cmd/error.go diff --git a/go.mod b/go.mod index d7c1e90..eb27c1e 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/conventionalcommit/commitlint -go 1.17 +go 1.23.0 require ( github.com/conventionalcommit/parser v0.7.1 - github.com/urfave/cli/v2 v2.11.1 - golang.org/x/mod v0.5.1 + github.com/urfave/cli/v2 v2.27.5 + golang.org/x/mod v0.21.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect ) diff --git a/go.sum b/go.sum index 503a67e..b3f0795 100644 --- a/go.sum +++ b/go.sum @@ -3,16 +3,24 @@ github.com/conventionalcommit/parser v0.7.1 h1:oAzcrEqyyGnzCeNOBqGx2qnxISxneUuBi github.com/conventionalcommit/parser v0.7.1/go.mod h1:k3teTA7nWpRrk7sjAihpAXm+1QLu1OscGrxclMHgEyc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/cmd/cli.go b/internal/cmd/cli.go index 76b0a99..d398263 100644 --- a/internal/cmd/cli.go +++ b/internal/cmd/cli.go @@ -4,7 +4,7 @@ package cmd import ( "fmt" - cli "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" "github.com/conventionalcommit/commitlint/internal" ) @@ -49,7 +49,8 @@ func newLintCmd() *cli.Command { Action: func(ctx *cli.Context) error { confFilePath := ctx.String("config") fileInput := ctx.String("message") - return lintMsg(confFilePath, fileInput) + err := lintMsg(confFilePath, fileInput) + return handleError(err, "Failed to run lint command") }, } } @@ -76,7 +77,7 @@ func newInitCmd() *cli.Command { hooksPath := ctx.String("hookspath") err := initLint(confPath, hooksPath, isGlobal, isReplace) - if err != nil { + if handleError(err, "Failed to initialize commitlint") != nil { if isHookExists(err) { fmt.Println("commitlint init failed") fmt.Println("run with --replace to replace existing files") @@ -112,7 +113,7 @@ func newConfigCmd() *cli.Command { isReplace := ctx.Bool("replace") fileName := ctx.String("file") err := configCreate(fileName, isReplace) - if err != nil { + if handleError(err, "Failed to create config file") != nil { if isConfExists(err) { fmt.Println("config create failed") fmt.Println("run with --replace to replace existing file") @@ -144,10 +145,10 @@ func newConfigCmd() *cli.Command { return nil } if len(errs) == 1 { - return errs[0] + return handleError(errs[0], "Config check failed") } merr := multiError(errs) - return &merr + return handleError(&merr, "Config check failed") }, } @@ -170,9 +171,9 @@ func newHookCmd() *cli.Command { isReplace := ctx.Bool("replace") hooksPath := ctx.String("hookspath") err := hookCreate(hooksPath, isReplace) - if err != nil { + if handleError(err, "Failed to create hooks") != nil { if isHookExists(err) { - fmt.Println("create failed. hook files already exists") + fmt.Println("create failed. hook files already exist") fmt.Println("run with --replace to replace existing hook files") return nil } @@ -195,7 +196,7 @@ func newDebugCmd() *cli.Command { Name: "debug", Usage: "prints useful information for debugging", Action: func(ctx *cli.Context) error { - return printDebug() + return handleError(printDebug(), "Debugging information failed") }, } } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 3e45424..eb58642 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -7,7 +7,8 @@ import ( // Run runs commitlint cli with os.Args func Run() error { - return newCliApp().Run(os.Args) + err := newCliApp().Run(os.Args) + return handleError(err, "Failed to run commitlint CLI") } type multiError []error diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 6ed43cd..28aab93 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -11,22 +11,23 @@ import ( // configCreate is the callback function for create config command func configCreate(fileName string, isReplace bool) (retErr error) { outPath := filepath.Join(".", fileName) + // if config file already exists skip creating or overwriting it if _, err := os.Stat(outPath); !os.IsNotExist(err) { if !isReplace { - return errConfigExist + return handleError(errConfigExist, "Config file already exists") } } outFilePath := filepath.Clean(outPath) f, err := os.Create(outFilePath) - if err != nil { + if handleError(err, "Failed to create config file") != nil { return err } defer func() { err := f.Close() if retErr == nil && err != nil { - retErr = err + retErr = handleError(err, "Failed to close config file") } }() @@ -34,18 +35,18 @@ func configCreate(fileName string, isReplace bool) (retErr error) { defer func() { err := w.Flush() if retErr == nil && err != nil { - retErr = err + retErr = handleError(err, "Failed to flush writer") } }() defConf := config.NewDefault() - return config.WriteTo(w, defConf) + return handleError(config.WriteTo(w, defConf), "Failed to write config to file") } // configCheck is the callback function for check/verify command func configCheck(confPath string) []error { conf, err := config.Parse(confPath) - if err != nil { + if handleError(err, "Failed to parse configuration file") != nil { return []error{err} } return config.Validate(conf) diff --git a/internal/cmd/debug.go b/internal/cmd/debug.go index 9923281..eebb8d7 100644 --- a/internal/cmd/debug.go +++ b/internal/cmd/debug.go @@ -17,22 +17,22 @@ func printDebug() error { w.WriteByte('\n') gitVer, err := getGitVersion() - if err != nil { + if handleError(err, "Failed to get Git version") != nil { return err } localConf, err := getGitHookConfig(false) - if err != nil { + if handleError(err, "Failed to get local Git hook configuration") != nil { return err } globalConf, err := getGitHookConfig(true) - if err != nil { + if handleError(err, "Failed to get global Git hook configuration") != nil { return err } confFile, confType, err := internal.LookupConfigPath() - if err != nil { + if handleError(err, "Failed to lookup configuration path") != nil { return err } @@ -67,7 +67,7 @@ func getGitVersion() (string, error) { cmd.Stdout = b cmd.Stderr = os.Stderr err := cmd.Run() - if err != nil { + if handleError(err, "Failed to execute 'git version' command") != nil { return "", err } @@ -90,7 +90,7 @@ func getGitHookConfig(isGlobal bool) (string, error) { cmd.Stdout = b err := cmd.Run() - if err != nil { + if handleError(err, "Failed to execute 'git config core.hooksPath' command") != nil { return "", err } diff --git a/internal/cmd/error.go b/internal/cmd/error.go new file mode 100644 index 0000000..10dfeca --- /dev/null +++ b/internal/cmd/error.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +// handleError handles and logs errors in a consistent way +func handleError(err error, customMessage string) error { + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s - %v\n", customMessage, err) + return cli.Exit(customMessage, 1) + } + return nil +} diff --git a/internal/cmd/hook.go b/internal/cmd/hook.go index 1d46c8c..be1d966 100644 --- a/internal/cmd/hook.go +++ b/internal/cmd/hook.go @@ -25,46 +25,50 @@ func hookCreate(hooksPath string, isReplace bool) error { hooksPath = filepath.Join(".", defaultHooksPath) } hooksPath = filepath.Clean(hooksPath) - return createHooks(hooksPath, isReplace) + return handleError(createHooks(hooksPath, isReplace), "Failed to create hooks") } func initHooks(confPath, hookFlag string, isGlobal, isReplace bool) (string, error) { hookDir, err := getHookDir(hookFlag, isGlobal) - if err != nil { + if handleError(err, "Failed to get hook directory") != nil { return "", err } err = writeHooks(hookDir, isReplace) - if err != nil { + if handleError(err, "Failed to write hooks") != nil { return "", err } return hookDir, nil } func createHooks(hookBaseDir string, isReplace bool) error { - return writeHooks(hookBaseDir, isReplace) + return handleError(writeHooks(hookBaseDir, isReplace), "Failed to write hooks to base directory") } func writeHooks(hookDir string, isReplace bool) error { // if commit-msg already exists skip creating or overwriting it if _, err := os.Stat(hookDir); !os.IsNotExist(err) { if !isReplace { - return errHooksExist + return handleError(errHooksExist, "Hook already exists and replace option not set") } } err := os.MkdirAll(hookDir, os.ModePerm) - if err != nil { + if handleError(err, "Failed to create hook directory") != nil { return err } // create hook file - return hook.WriteHooks(hookDir) + return handleError(hook.WriteHooks(hookDir), "Failed to write hooks to directory") } func getHookDir(hookFlag string, isGlobal bool) (string, error) { if hookFlag != "" { - return filepath.Abs(hookFlag) + absPath, err := filepath.Abs(hookFlag) + if handleError(err, "Failed to get absolute path for hook directory") != nil { + return "", err + } + return absPath, nil } hookFlag = defaultHooksPath @@ -72,7 +76,7 @@ func getHookDir(hookFlag string, isGlobal bool) (string, error) { if isGlobal { // get user home dir homeDir, err := os.UserHomeDir() - if err != nil { + if handleError(err, "Failed to get user home directory") != nil { return "", err } @@ -82,7 +86,7 @@ func getHookDir(hookFlag string, isGlobal bool) (string, error) { } gitDir, err := getRepoRootDir() - if err != nil { + if handleError(err, "Failed to get repository root directory") != nil { return "", err } return filepath.Join(gitDir, hookFlag), nil @@ -96,7 +100,7 @@ func getRepoRootDir() (string, error) { cmd.Stderr = os.Stderr err := cmd.Run() - if err != nil { + if handleError(err, "Failed to get repository root directory with git command") != nil { return "", err } @@ -109,9 +113,9 @@ func getRepoRootDir() (string, error) { } func isHookExists(err error) bool { - return err == errHooksExist + return errors.Is(err, errHooksExist) } func isConfExists(err error) bool { - return err == errConfigExist + return errors.Is(err, errConfigExist) } diff --git a/internal/cmd/init.go b/internal/cmd/init.go index b6dbd06..eaaff87 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -5,13 +5,13 @@ import ( "os/exec" ) -// initLint is the callback function for init command +// initLint is the callback function for the init command func initLint(confPath, hooksPath string, isGlobal, isReplace bool) error { hookDir, err := initHooks(confPath, hooksPath, isGlobal, isReplace) - if err != nil { + if handleError(err, "Failed to initialize hooks") != nil { return err } - return setGitConf(hookDir, isGlobal) + return handleError(setGitConf(hookDir, isGlobal), "Failed to set git configuration") } func setGitConf(hookDir string, isGlobal bool) error { @@ -23,5 +23,5 @@ func setGitConf(hookDir string, isGlobal bool) error { cmd := exec.Command("git", args...) cmd.Stderr = os.Stderr - return cmd.Run() + return handleError(cmd.Run(), "Failed to execute git config command") } diff --git a/internal/cmd/lint.go b/internal/cmd/lint.go index f2bafdd..da6e206 100644 --- a/internal/cmd/lint.go +++ b/internal/cmd/lint.go @@ -7,14 +7,13 @@ import ( "path/filepath" "strings" - cli "github.com/urfave/cli/v2" - "github.com/conventionalcommit/commitlint/config" "github.com/conventionalcommit/commitlint/lint" + "github.com/urfave/cli/v2" ) const ( - // errExitCode represent error exit code + // errExitCode represents the error exit code errExitCode = 1 ) @@ -22,8 +21,8 @@ const ( func lintMsg(confPath, msgPath string) error { // NOTE: lint should return with exit code for error case resStr, hasError, err := runLint(confPath, msgPath) - if err != nil { - return cli.Exit(err, errExitCode) + if handleError(err, "Linting failed") != nil { + return err } if hasError { @@ -37,40 +36,41 @@ func lintMsg(confPath, msgPath string) error { func runLint(confFilePath, fileInput string) (lintResult string, hasError bool, err error) { linter, format, err := getLinter(confFilePath) - if err != nil { + if handleError(err, "Failed to create linter") != nil { return "", false, err } commitMsg, err := getCommitMsg(fileInput) - if err != nil { + if handleError(err, "Failed to read commit message") != nil { return "", false, err } result, err := linter.ParseAndLint(commitMsg) - if err != nil { + if handleError(err, "Linting process failed") != nil { return "", false, err } output, err := format.Format(result) - if err != nil { + if handleError(err, "Formatting result failed") != nil { return "", false, err } + return output, hasErrorSeverity(result), nil } func getLinter(confParam string) (*lint.Linter, lint.Formatter, error) { conf, err := getConfig(confParam) - if err != nil { + if handleError(err, "Failed to get configuration") != nil { return nil, nil, err } format, err := config.GetFormatter(conf) - if err != nil { + if handleError(err, "Failed to get formatter") != nil { return nil, nil, err } linter, err := config.NewLinter(conf) - if err != nil { + if handleError(err, "Failed to create new linter") != nil { return nil, nil, err } @@ -85,7 +85,7 @@ func getConfig(confParam string) (*lint.Config, error) { // If config param is empty, lookup for defaults conf, err := config.LookupAndParse() - if err != nil { + if handleError(err, "Failed to lookup and parse configuration") != nil { return nil, err } @@ -94,7 +94,7 @@ func getConfig(confParam string) (*lint.Config, error) { func getCommitMsg(fileInput string) (string, error) { commitMsg, err := readStdInPipe() - if err != nil { + if handleError(err, "Failed to read commit message from stdin") != nil { return "", err } @@ -109,7 +109,7 @@ func getCommitMsg(fileInput string) (string, error) { fileInput = filepath.Clean(fileInput) inBytes, err := os.ReadFile(fileInput) - if err != nil { + if handleError(err, "Failed to read commit message file") != nil { return "", err } return string(inBytes), nil @@ -117,7 +117,7 @@ func getCommitMsg(fileInput string) (string, error) { func readStdInPipe() (string, error) { stat, err := os.Stdin.Stat() - if err != nil { + if handleError(err, "Failed to read stdin pipe status") != nil { return "", err } @@ -129,7 +129,7 @@ func readStdInPipe() (string, error) { // user input from stdin pipe readBytes, err := io.ReadAll(os.Stdin) - if err != nil { + if handleError(err, "Failed to read from stdin pipe") != nil { return "", err } s := string(readBytes)