From 9781f876e077dbf14ff8727577e15943478c3f46 Mon Sep 17 00:00:00 2001 From: Michael Chernov <4ernovm@gmail.com> Date: Thu, 14 Dec 2023 19:30:30 +0200 Subject: [PATCH 1/4] Enhance Yarn errors --- internal/resolution/pm/yarn/job.go | 186 ++++++++++++++++++++++-- internal/resolution/pm/yarn/job_test.go | 76 +++++++--- 2 files changed, 230 insertions(+), 32 deletions(-) diff --git a/internal/resolution/pm/yarn/job.go b/internal/resolution/pm/yarn/job.go index 929b918f..8dbe65de 100644 --- a/internal/resolution/pm/yarn/job.go +++ b/internal/resolution/pm/yarn/job.go @@ -1,12 +1,22 @@ package yarn import ( + "regexp" + "strings" + "github.com/debricked/cli/internal/resolution/job" "github.com/debricked/cli/internal/resolution/pm/util" ) const ( - yarn = "yarn" + yarn = "yarn" + invalidJsonErrRegex = "error SyntaxError.*package.json: (.*)" + invalidSchemaErrRegex = "error package.json: (.*)" + invalidArgumentErrRegex = "error TypeError \\[\\w+\\]: (.*)" + versionNotFoundErrRegex = "error (Couldn\\'t find any versions for .*)" + dependencyNotFoundErrRegex = "error Error: (.*): Not found" + registryUnavailableErrRegex = "error Error: getaddrinfo ENOTFOUND ([\\w\\.]+)" + permissionDeniedErrRegex = "Error: (.*): Request failed \"404 Not Found\"" ) type Job struct { @@ -34,31 +44,179 @@ func (j *Job) Install() bool { func (j *Job) Run() { if j.install { + status := "installing dependencies" + j.SendStatus(status) + j.yarnCommand = yarn + + installCmd, err := j.cmdFactory.MakeInstallCmd(j.yarnCommand, j.GetFile()) - j.SendStatus("installing dependencies") - _, err := j.runInstallCmd() if err != nil { - jobError := util.NewPMJobError(err.Error()) - j.Errors().Critical(jobError) + cmdError := util.NewPMJobError(err.Error()) + cmdError.SetCommand(installCmd.String()) + cmdError.SetStatus(status) + j.handleError(cmdError) + + return + } + + _, err = installCmd.Output() + + if err != nil { + cmdError := util.NewPMJobError(err.Error()) + cmdError.SetCommand(installCmd.String()) + cmdError.SetStatus(status) + j.handleError(cmdError) + + return + } + } +} + +func (j *Job) handleError(cmdErr job.IError) { + expressions := []string{ + invalidJsonErrRegex, + invalidSchemaErrRegex, + invalidArgumentErrRegex, + versionNotFoundErrRegex, + dependencyNotFoundErrRegex, + registryUnavailableErrRegex, + permissionDeniedErrRegex, + } + + for _, expression := range expressions { + regex := regexp.MustCompile(expression) + matches := regex.FindAllStringSubmatch(cmdErr.Error(), -1) + + if len(matches) > 0 { + cmdErr = j.addDocumentation(expression, matches, cmdErr) + j.Errors().Append(cmdErr) return } } + j.Errors().Append(cmdErr) +} + +func (j *Job) addDocumentation(expr string, matches [][]string, cmdErr job.IError) job.IError { + documentation := cmdErr.Documentation() + + switch { + case expr == invalidJsonErrRegex: + documentation = getInvalidJsonErrorDocumentation(matches, cmdErr) + case expr == invalidSchemaErrRegex: + documentation = getInvalidSchemaErrorDocumentation(matches, cmdErr) + case expr == invalidArgumentErrRegex: + documentation = getInvalidArgumentErrorDocumentation(matches, cmdErr) + case expr == versionNotFoundErrRegex: + documentation = getVersionNotFoundErrorDocumentation(matches, cmdErr) + case expr == dependencyNotFoundErrRegex: + documentation = getDependencyNotFoundErrorDocumentation(matches, cmdErr) + case expr == registryUnavailableErrRegex: + documentation = getRegistryUnavailableErrorDocumentation(matches, cmdErr) + case expr == permissionDeniedErrRegex: + documentation = getPermissionDeniedErrorDocumentation(matches, cmdErr) + } + + cmdErr.SetDocumentation(documentation) + + return cmdErr +} + +func getInvalidJsonErrorDocumentation(matches [][]string, err job.IError) string { + message := "" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] + } + + return strings.Join( + []string{ + "Your package.json file contains invalid JSON:", + message + ".", + }, " ") +} + +func getInvalidSchemaErrorDocumentation(matches [][]string, err job.IError) string { + message := "" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] + } + + return strings.Join( + []string{ + "Your package.json file is not valid:", + message + ".", + "Please make sure it follows the schema.", + }, " ") +} + +func getInvalidArgumentErrorDocumentation(matches [][]string, err job.IError) string { + message := "" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] + } + + return strings.Join( + []string{ + message + ".", + "Please make sure that your package.json file doesn't contain errors.", + }, " ") } -func (j *Job) runInstallCmd() ([]byte, error) { +func getDependencyNotFoundErrorDocumentation(matches [][]string, err job.IError) string { + dependency := "" + if len(matches) > 0 && len(matches[0]) > 1 { + dependency = matches[0][1] + } + + return strings.Join( + []string{ + "Failed to find package", + "\"" + dependency + "\"", + "that satisfies the requirement from yarn dependencies.", + "Please check that dependencies are correct in your package.json file.", + }, " ") +} - j.yarnCommand = yarn - installCmd, err := j.cmdFactory.MakeInstallCmd(j.yarnCommand, j.GetFile()) - if err != nil { - return nil, err +func getVersionNotFoundErrorDocumentation(matches [][]string, err job.IError) string { + message := "" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] } - installCmdOutput, err := installCmd.Output() - if err != nil { - return nil, j.GetExitError(err) + return strings.Join( + []string{ + message + ".", + "Please check that dependencies are correct in your package.json file.", + }, " ") +} + +func getRegistryUnavailableErrorDocumentation(matches [][]string, err job.IError) string { + registry := "" + if len(matches) > 0 && len(matches[0]) > 1 { + registry = matches[0][1] + } + + return strings.Join( + []string{ + "Package registry", + "\"" + registry + "\"", + "is not available at the moment.", + "There might be a trouble with your network connection.", + }, " ") +} + +func getPermissionDeniedErrorDocumentation(matches [][]string, err job.IError) string { + dependency := "" + if len(matches) > 0 && len(matches[0]) > 1 { + dependency = matches[0][1] } - return installCmdOutput, nil + return strings.Join( + []string{ + "Failed to find a package that satisfies requirements for yarn dependencies:", + dependency + ".", + "This could mean that the package or version does not exist or is private.\n", + util.InstallPrivateDependencyMessage, + }, " ") } diff --git a/internal/resolution/pm/yarn/job_test.go b/internal/resolution/pm/yarn/job_test.go index 47b5da48..c5dfb0e4 100644 --- a/internal/resolution/pm/yarn/job_test.go +++ b/internal/resolution/pm/yarn/job_test.go @@ -22,16 +22,6 @@ func TestNewJob(t *testing.T) { assert.False(t, j.Errors().HasError()) } -func TestRunInstall(t *testing.T) { - cmdFactoryMock := testdata.NewEchoCmdFactory() - j := NewJob("file", false, cmdFactoryMock) - - _, err := j.runInstallCmd() - assert.NoError(t, err) - - assert.False(t, j.Errors().HasError()) -} - func TestInstall(t *testing.T) { j := Job{install: true} assert.Equal(t, true, j.Install()) @@ -41,16 +31,66 @@ func TestInstall(t *testing.T) { } func TestRunInstallCmdErr(t *testing.T) { - cmdErr := errors.New("cmd-error") - cmdFactoryMock := testdata.NewEchoCmdFactory() - cmdFactoryMock.MakeInstallErr = cmdErr - j := NewJob("file", true, cmdFactoryMock) + cases := []struct { + cmd string + error string + doc string + }{ + { + error: "cmd-error", + doc: util.UnknownError, + }, + { + error: "error SyntaxError: /home/asus/Projects/playground/rpn_js/package.json: Unexpected string in JSON at position 186\n at JSON.parse ()", + doc: "Your package.json file contains invalid JSON: Unexpected string in JSON at position 186.", + }, + { + error: "error package.json: \"name\" is not a string", + doc: "Your package.json file is not valid: \"name\" is not a string. Please make sure it follows the schema.", + }, + { + error: "error TypeError [ERR_INVALID_ARG_TYPE]: The \"path\" argument must be of type string. Received an instance of Array\n at validateString (internal/validators.js:120:11)\n", + doc: "The \"path\" argument must be of type string. Received an instance of Array. Please make sure that your package.json file doesn't contain errors.", + }, + { + error: "error Error: https://registry.yarnpkg.com/chalke: Not found\n at Request.params.callback [as _callback] (/usr/local/lib/node_modules/yarn/lib/cli.js:66148:18)", + doc: "Failed to find package \"https://registry.yarnpkg.com/chalke\" that satisfies the requirement from yarn dependencies. Please check that dependencies are correct in your package.json file.", + }, + { + error: "error Couldn't find any versions for \"chalk\" that matches \"^300.0.0\"\ninfo Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.", + doc: "Couldn't find any versions for \"chalk\" that matches \"^300.0.0\". Please check that dependencies are correct in your package.json file.", + }, + { + error: "error Error: getaddrinfo ENOTFOUND nexus.dev\n at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)\n", + doc: "Package registry \"nexus.dev\" is not available at the moment. There might be a trouble with your network connection.", + }, + { + error: "Error: https://registry.npmjs.org/@private/my-private-package/-/my-private-package-0.0.5.tgz: Request failed \"404 Not Found\"", + doc: "Failed to find a package that satisfies requirements for yarn dependencies: https://registry.npmjs.org/@private/my-private-package/-/my-private-package-0.0.5.tgz. This could mean that the package or version does not exist or is private.\n If this is a private dependency, please make sure that the debricked CLI has access to install it or pre-install it before running the debricked CLI.", + }, + } - go jobTestdata.WaitStatus(j) - j.Run() + for _, c := range cases { + cmdErr := errors.New(c.error) + cmdFactoryMock := testdata.NewEchoCmdFactory() + cmdFactoryMock.MakeInstallErr = cmdErr + cmd, _ := cmdFactoryMock.MakeInstallCmd("echo", "package.json") + + expectedError := util.NewPMJobError(c.error) + expectedError.SetDocumentation(c.doc) + expectedError.SetStatus("installing dependencies") + expectedError.SetCommand(cmd.String()) + + j := NewJob("file", true, cmdFactoryMock) + + go jobTestdata.WaitStatus(j) + j.Run() + + errors := j.Errors().GetAll() - assert.Len(t, j.Errors().GetAll(), 1) - assert.Contains(t, j.Errors().GetAll(), util.NewPMJobError(cmdErr.Error())) + assert.Len(t, j.Errors().GetAll(), 1) + assert.Contains(t, errors, expectedError) + } } func TestRunInstallCmdOutputErr(t *testing.T) { From 5570821f6d48bcf4b014a1ede1d9b28951c13165 Mon Sep 17 00:00:00 2001 From: Michael Chernov <4ernovm@gmail.com> Date: Thu, 14 Dec 2023 19:36:46 +0200 Subject: [PATCH 2/4] Improve document functions --- internal/resolution/pm/yarn/job.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/resolution/pm/yarn/job.go b/internal/resolution/pm/yarn/job.go index 8dbe65de..a70ea15d 100644 --- a/internal/resolution/pm/yarn/job.go +++ b/internal/resolution/pm/yarn/job.go @@ -103,19 +103,19 @@ func (j *Job) addDocumentation(expr string, matches [][]string, cmdErr job.IErro switch { case expr == invalidJsonErrRegex: - documentation = getInvalidJsonErrorDocumentation(matches, cmdErr) + documentation = getInvalidJsonErrorDocumentation(matches) case expr == invalidSchemaErrRegex: - documentation = getInvalidSchemaErrorDocumentation(matches, cmdErr) + documentation = getInvalidSchemaErrorDocumentation(matches) case expr == invalidArgumentErrRegex: - documentation = getInvalidArgumentErrorDocumentation(matches, cmdErr) + documentation = getInvalidArgumentErrorDocumentation(matches) case expr == versionNotFoundErrRegex: - documentation = getVersionNotFoundErrorDocumentation(matches, cmdErr) + documentation = getVersionNotFoundErrorDocumentation(matches) case expr == dependencyNotFoundErrRegex: - documentation = getDependencyNotFoundErrorDocumentation(matches, cmdErr) + documentation = getDependencyNotFoundErrorDocumentation(matches) case expr == registryUnavailableErrRegex: - documentation = getRegistryUnavailableErrorDocumentation(matches, cmdErr) + documentation = getRegistryUnavailableErrorDocumentation(matches) case expr == permissionDeniedErrRegex: - documentation = getPermissionDeniedErrorDocumentation(matches, cmdErr) + documentation = getPermissionDeniedErrorDocumentation(matches) } cmdErr.SetDocumentation(documentation) @@ -123,7 +123,7 @@ func (j *Job) addDocumentation(expr string, matches [][]string, cmdErr job.IErro return cmdErr } -func getInvalidJsonErrorDocumentation(matches [][]string, err job.IError) string { +func getInvalidJsonErrorDocumentation(matches [][]string) string { message := "" if len(matches) > 0 && len(matches[0]) > 1 { message = matches[0][1] @@ -136,7 +136,7 @@ func getInvalidJsonErrorDocumentation(matches [][]string, err job.IError) string }, " ") } -func getInvalidSchemaErrorDocumentation(matches [][]string, err job.IError) string { +func getInvalidSchemaErrorDocumentation(matches [][]string) string { message := "" if len(matches) > 0 && len(matches[0]) > 1 { message = matches[0][1] @@ -150,7 +150,7 @@ func getInvalidSchemaErrorDocumentation(matches [][]string, err job.IError) stri }, " ") } -func getInvalidArgumentErrorDocumentation(matches [][]string, err job.IError) string { +func getInvalidArgumentErrorDocumentation(matches [][]string) string { message := "" if len(matches) > 0 && len(matches[0]) > 1 { message = matches[0][1] @@ -163,7 +163,7 @@ func getInvalidArgumentErrorDocumentation(matches [][]string, err job.IError) st }, " ") } -func getDependencyNotFoundErrorDocumentation(matches [][]string, err job.IError) string { +func getDependencyNotFoundErrorDocumentation(matches [][]string) string { dependency := "" if len(matches) > 0 && len(matches[0]) > 1 { dependency = matches[0][1] @@ -178,7 +178,7 @@ func getDependencyNotFoundErrorDocumentation(matches [][]string, err job.IError) }, " ") } -func getVersionNotFoundErrorDocumentation(matches [][]string, err job.IError) string { +func getVersionNotFoundErrorDocumentation(matches [][]string) string { message := "" if len(matches) > 0 && len(matches[0]) > 1 { message = matches[0][1] @@ -191,7 +191,7 @@ func getVersionNotFoundErrorDocumentation(matches [][]string, err job.IError) st }, " ") } -func getRegistryUnavailableErrorDocumentation(matches [][]string, err job.IError) string { +func getRegistryUnavailableErrorDocumentation(matches [][]string) string { registry := "" if len(matches) > 0 && len(matches[0]) > 1 { registry = matches[0][1] @@ -206,7 +206,7 @@ func getRegistryUnavailableErrorDocumentation(matches [][]string, err job.IError }, " ") } -func getPermissionDeniedErrorDocumentation(matches [][]string, err job.IError) string { +func getPermissionDeniedErrorDocumentation(matches [][]string) string { dependency := "" if len(matches) > 0 && len(matches[0]) > 1 { dependency = matches[0][1] From 351dc94fca6c191bb0aa5b000b7f48f0ee438114 Mon Sep 17 00:00:00 2001 From: Michael Chernov <4ernovm@gmail.com> Date: Mon, 18 Dec 2023 16:01:33 +0200 Subject: [PATCH 3/4] Fix Yarn error parsing --- internal/resolution/pm/yarn/job.go | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/internal/resolution/pm/yarn/job.go b/internal/resolution/pm/yarn/job.go index a70ea15d..ebc45400 100644 --- a/internal/resolution/pm/yarn/job.go +++ b/internal/resolution/pm/yarn/job.go @@ -51,28 +51,29 @@ func (j *Job) Run() { installCmd, err := j.cmdFactory.MakeInstallCmd(j.yarnCommand, j.GetFile()) if err != nil { - cmdError := util.NewPMJobError(err.Error()) - cmdError.SetCommand(installCmd.String()) - cmdError.SetStatus(status) - j.handleError(cmdError) + j.handleError(j.createError(err.Error(), installCmd.String(), status)) return } - _, err = installCmd.Output() - - if err != nil { - cmdError := util.NewPMJobError(err.Error()) - cmdError.SetCommand(installCmd.String()) - cmdError.SetStatus(status) - j.handleError(cmdError) + if output, err := installCmd.Output(); err != nil { + error := strings.Join([]string{string(output), j.GetExitError(err).Error()}, "") + j.handleError(j.createError(error, installCmd.String(), status)) return } } } -func (j *Job) handleError(cmdErr job.IError) { +func (j *Job) createError(error string, cmd string, status string) job.IError { + cmdError := util.NewPMJobError(error) + cmdError.SetCommand(cmd) + cmdError.SetStatus(status) + + return cmdError +} + +func (j *Job) handleError(cmdError job.IError) { expressions := []string{ invalidJsonErrRegex, invalidSchemaErrRegex, @@ -85,21 +86,21 @@ func (j *Job) handleError(cmdErr job.IError) { for _, expression := range expressions { regex := regexp.MustCompile(expression) - matches := regex.FindAllStringSubmatch(cmdErr.Error(), -1) + matches := regex.FindAllStringSubmatch(cmdError.Error(), -1) if len(matches) > 0 { - cmdErr = j.addDocumentation(expression, matches, cmdErr) - j.Errors().Append(cmdErr) + cmdError = j.addDocumentation(expression, matches, cmdError) + j.Errors().Append(cmdError) return } } - j.Errors().Append(cmdErr) + j.Errors().Append(cmdError) } -func (j *Job) addDocumentation(expr string, matches [][]string, cmdErr job.IError) job.IError { - documentation := cmdErr.Documentation() +func (j *Job) addDocumentation(expr string, matches [][]string, cmdError job.IError) job.IError { + documentation := cmdError.Documentation() switch { case expr == invalidJsonErrRegex: @@ -118,9 +119,9 @@ func (j *Job) addDocumentation(expr string, matches [][]string, cmdErr job.IErro documentation = getPermissionDeniedErrorDocumentation(matches) } - cmdErr.SetDocumentation(documentation) + cmdError.SetDocumentation(documentation) - return cmdErr + return cmdError } func getInvalidJsonErrorDocumentation(matches [][]string) string { From d475642e166c4a368a0aa6c9d290e0d11742e88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20W=C3=A5reus?= Date: Mon, 18 Dec 2023 18:49:58 +0100 Subject: [PATCH 4/4] Fix dependency not found error message format --- internal/resolution/pm/yarn/job.go | 3 ++- internal/resolution/pm/yarn/job_test.go | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/resolution/pm/yarn/job.go b/internal/resolution/pm/yarn/job.go index ebc45400..16558b2f 100644 --- a/internal/resolution/pm/yarn/job.go +++ b/internal/resolution/pm/yarn/job.go @@ -14,7 +14,7 @@ const ( invalidSchemaErrRegex = "error package.json: (.*)" invalidArgumentErrRegex = "error TypeError \\[\\w+\\]: (.*)" versionNotFoundErrRegex = "error (Couldn\\'t find any versions for .*)" - dependencyNotFoundErrRegex = "error Error: (.*): Not found" + dependencyNotFoundErrRegex = `error.*? "?(https?://[^"\s:]+)?: Not found` registryUnavailableErrRegex = "error Error: getaddrinfo ENOTFOUND ([\\w\\.]+)" permissionDeniedErrRegex = "Error: (.*): Request failed \"404 Not Found\"" ) @@ -176,6 +176,7 @@ func getDependencyNotFoundErrorDocumentation(matches [][]string) string { "\"" + dependency + "\"", "that satisfies the requirement from yarn dependencies.", "Please check that dependencies are correct in your package.json file.", + "\n" + util.InstallPrivateDependencyMessage, }, " ") } diff --git a/internal/resolution/pm/yarn/job_test.go b/internal/resolution/pm/yarn/job_test.go index c5dfb0e4..783a6f45 100644 --- a/internal/resolution/pm/yarn/job_test.go +++ b/internal/resolution/pm/yarn/job_test.go @@ -54,7 +54,11 @@ func TestRunInstallCmdErr(t *testing.T) { }, { error: "error Error: https://registry.yarnpkg.com/chalke: Not found\n at Request.params.callback [as _callback] (/usr/local/lib/node_modules/yarn/lib/cli.js:66148:18)", - doc: "Failed to find package \"https://registry.yarnpkg.com/chalke\" that satisfies the requirement from yarn dependencies. Please check that dependencies are correct in your package.json file.", + doc: "Failed to find package \"https://registry.yarnpkg.com/chalke\" that satisfies the requirement from yarn dependencies. Please check that dependencies are correct in your package.json file. \nIf this is a private dependency, please make sure that the debricked CLI has access to install it or pre-install it before running the debricked CLI.", + }, + { + error: `error An unexpected error occurred: "https://registry.yarnpkg.com/chalke: Not found".`, + doc: "Failed to find package \"https://registry.yarnpkg.com/chalke\" that satisfies the requirement from yarn dependencies. Please check that dependencies are correct in your package.json file. \nIf this is a private dependency, please make sure that the debricked CLI has access to install it or pre-install it before running the debricked CLI.", }, { error: "error Couldn't find any versions for \"chalk\" that matches \"^300.0.0\"\ninfo Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.",