github.com/jaylevin/jenkins-library@v1.230.4/cmd/npmExecuteLint.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/SAP/jenkins-library/pkg/command" 13 piperhttp "github.com/SAP/jenkins-library/pkg/http" 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/SAP/jenkins-library/pkg/npm" 16 "github.com/SAP/jenkins-library/pkg/piperutils" 17 "github.com/SAP/jenkins-library/pkg/telemetry" 18 ) 19 20 type lintUtils interface { 21 Glob(pattern string) (matches []string, err error) 22 23 getExecRunner() command.ExecRunner 24 getGeneralPurposeConfig(configURL string) 25 } 26 27 type lintUtilsBundle struct { 28 *piperutils.Files 29 execRunner *command.Command 30 client *piperhttp.Client 31 } 32 33 func newLintUtilsBundle() *lintUtilsBundle { 34 return &lintUtilsBundle{ 35 Files: &piperutils.Files{}, 36 client: &piperhttp.Client{}, 37 } 38 } 39 40 func (u *lintUtilsBundle) getExecRunner() command.ExecRunner { 41 if u.execRunner == nil { 42 u.execRunner = &command.Command{} 43 u.execRunner.Stdout(log.Writer()) 44 u.execRunner.Stderr(log.Writer()) 45 } 46 return u.execRunner 47 } 48 49 func (u *lintUtilsBundle) getGeneralPurposeConfig(configURL string) { 50 response, err := u.client.SendRequest(http.MethodGet, configURL, nil, nil, nil) 51 if err != nil { 52 log.Entry().Warnf("failed to download general purpose configuration: %v", err) 53 return 54 } 55 56 defer response.Body.Close() 57 58 content, err := ioutil.ReadAll(response.Body) 59 if err != nil { 60 log.Entry().Warnf("error while reading the general purpose configuration: %v", err) 61 return 62 } 63 64 err = u.FileWrite(filepath.Join(".pipeline", ".eslintrc.json"), content, os.ModePerm) 65 if err != nil { 66 log.Entry().Warnf("failed to write .eslintrc.json file to .pipeline/: %v", err) 67 } 68 } 69 70 func npmExecuteLint(config npmExecuteLintOptions, telemetryData *telemetry.CustomData) { 71 utils := newLintUtilsBundle() 72 npmExecutorOptions := npm.ExecutorOptions{DefaultNpmRegistry: config.DefaultNpmRegistry, ExecRunner: utils.getExecRunner()} 73 npmExecutor := npm.NewExecutor(npmExecutorOptions) 74 75 err := runNpmExecuteLint(npmExecutor, utils, &config) 76 if err != nil { 77 log.Entry().WithError(err).Fatal("step execution failed") 78 } 79 } 80 81 func runNpmExecuteLint(npmExecutor npm.Executor, utils lintUtils, config *npmExecuteLintOptions) error { 82 if len(config.RunScript) == 0 { 83 return fmt.Errorf("runScript is not allowed to be empty!") 84 } 85 86 packageJSONFiles := npmExecutor.FindPackageJSONFiles() 87 packagesWithLintScript, _ := npmExecutor.FindPackageJSONFilesWithScript(packageJSONFiles, config.RunScript) 88 89 if len(packagesWithLintScript) > 0 { 90 if config.Install { 91 err := npmExecutor.InstallAllDependencies(packagesWithLintScript) 92 if err != nil { 93 return err 94 } 95 } 96 97 err := runLintScript(npmExecutor, config.RunScript, config.FailOnError) 98 if err != nil { 99 return err 100 } 101 } else { 102 if config.Install { 103 err := npmExecutor.InstallAllDependencies(packageJSONFiles) 104 if err != nil { 105 return err 106 } 107 } 108 109 err := runDefaultLint(npmExecutor, utils, config.FailOnError) 110 if err != nil { 111 return err 112 } 113 } 114 return nil 115 } 116 117 func runLintScript(npmExecutor npm.Executor, runScript string, failOnError bool) error { 118 runScripts := []string{runScript} 119 runOptions := []string{"--silent"} 120 121 err := npmExecutor.RunScriptsInAllPackages(runScripts, runOptions, nil, false, nil, nil) 122 if err != nil { 123 if failOnError { 124 return fmt.Errorf("%s script execution failed with error: %w. This might be the result of severe linting findings, or some other issue while executing the script. Please examine the linting results in the UI, the cilint.xml file, if available, or the log above. ", runScript, err) 125 } 126 } 127 return nil 128 } 129 130 func runDefaultLint(npmExecutor npm.Executor, utils lintUtils, failOnError bool) error { 131 execRunner := utils.getExecRunner() 132 eslintConfigs := findEslintConfigs(utils) 133 134 err := npmExecutor.SetNpmRegistries() 135 if err != nil { 136 log.Entry().Warnf("failed to set npm registries before running default lint: %v", err) 137 } 138 139 // If the user has ESLint configs in the project we use them to lint existing JS files. In this case we do not lint other types of files, 140 // i.e., .jsx, .ts, .tsx, since we can not be sure that the provided config enables parsing of these file types. 141 if len(eslintConfigs) > 0 { 142 for i, config := range eslintConfigs { 143 dir := filepath.Dir(config) 144 if dir == "." { 145 err = execRunner.RunExecutable("npx", "eslint", ".", "-f", "checkstyle", "-o", "./"+strconv.Itoa(i)+"_defaultlint.xml", "--ignore-pattern", "node_modules/", "--ignore-pattern", ".eslintrc.js") 146 } else { 147 lintPattern := dir + "/**/*.js" 148 err = execRunner.RunExecutable("npx", "eslint", lintPattern, "-f", "checkstyle", "-o", "./"+strconv.Itoa(i)+"_defaultlint.xml", "--ignore-pattern", "node_modules/", "--ignore-pattern", ".eslintrc.js") 149 } 150 if err != nil { 151 if failOnError { 152 return fmt.Errorf("Lint execution failed. This might be the result of severe linting findings, problems with the provided ESLint configuration (%s), or another issue. Please examine the linting results in the UI or in %s, if available, or the log above. ", config, strconv.Itoa(i)+"_defaultlint.xml") 153 } 154 } 155 } 156 } else { 157 // install dependencies manually, since npx cannot resolve the dependencies required for general purpose 158 // ESLint config, e.g., TypeScript ESLint plugin 159 log.Entry().Info("Run ESLint with general purpose config") 160 utils.getGeneralPurposeConfig("https://raw.githubusercontent.com/SAP/jenkins-library/master/resources/.eslintrc.json") 161 162 // Ignore possible errors when invoking ESLint to not fail the pipeline based on linting results 163 _ = execRunner.RunExecutable("npm", "install", "eslint@^7.0.0", "typescript@^3.7.4", "@typescript-eslint/parser@^3.0.0", "@typescript-eslint/eslint-plugin@^3.0.0") 164 _ = execRunner.RunExecutable("npx", "--no-install", "eslint", ".", "--ext", ".js,.jsx,.ts,.tsx", "-c", ".pipeline/.eslintrc.json", "-f", "checkstyle", "-o", "./defaultlint.xml", "--ignore-pattern", ".eslintrc.js") 165 } 166 return nil 167 } 168 169 func findEslintConfigs(utils lintUtils) []string { 170 unfilteredListOfEslintConfigs, _ := utils.Glob("**/.eslintrc.*") 171 172 var eslintConfigs []string 173 174 for _, config := range unfilteredListOfEslintConfigs { 175 if strings.Contains(config, "node_modules") { 176 continue 177 } 178 179 if strings.HasPrefix(config, ".pipeline"+string(os.PathSeparator)) { 180 continue 181 } 182 183 eslintConfigs = append(eslintConfigs, config) 184 log.Entry().Info("Discovered ESLint config " + config) 185 } 186 return eslintConfigs 187 }