github.com/SAP/jenkins-library@v1.362.0/cmd/npmExecuteLint.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io" 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 := io.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, config.OutputFormat, config.OutputFileName) 110 111 if err != nil { 112 return err 113 } 114 } 115 return nil 116 } 117 118 func runLintScript(npmExecutor npm.Executor, runScript string, failOnError bool) error { 119 runScripts := []string{runScript} 120 runOptions := []string{"--silent"} 121 122 err := npmExecutor.RunScriptsInAllPackages(runScripts, runOptions, nil, false, nil, nil) 123 if err != nil { 124 if failOnError { 125 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) 126 } 127 } 128 return nil 129 } 130 131 func runDefaultLint(npmExecutor npm.Executor, utils lintUtils, failOnError bool, outputFormat string, outputFileName string) error { 132 execRunner := utils.getExecRunner() 133 eslintConfigs := findEslintConfigs(utils) 134 135 err := npmExecutor.SetNpmRegistries() 136 if err != nil { 137 log.Entry().Warnf("failed to set npm registries before running default lint: %v", err) 138 } 139 140 // 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, 141 // i.e., .jsx, .ts, .tsx, since we can not be sure that the provided config enables parsing of these file types. 142 if len(eslintConfigs) > 0 { 143 for i, config := range eslintConfigs { 144 lintPattern := "." 145 dir := filepath.Dir(config) 146 if dir != "." { 147 lintPattern = dir + "/**/*.js" 148 } 149 150 args := prepareArgs([]string{ 151 "eslint", 152 lintPattern, 153 "-f", outputFormat, 154 "--ignore-pattern", "node_modules/", 155 "--ignore-pattern", ".eslintrc.js", 156 }, fmt.Sprintf("./%s_%%s", strconv.Itoa(i)), outputFileName) 157 158 err = execRunner.RunExecutable("npx", args...) 159 if err != nil { 160 if failOnError { 161 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") 162 } 163 } 164 } 165 } else { 166 // install dependencies manually, since npx cannot resolve the dependencies required for general purpose 167 // ESLint config, e.g., TypeScript ESLint plugin 168 log.Entry().Info("Run ESLint with general purpose config") 169 generalPurposeLintConfigURI := "https://raw.githubusercontent.com/SAP/jenkins-library/master/resources/.eslintrc.json" 170 utils.getGeneralPurposeConfig(generalPurposeLintConfigURI) 171 172 err = 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") 173 if err != nil { 174 if failOnError { 175 return fmt.Errorf("linter installation failed: %s", err) 176 } 177 } 178 179 args := prepareArgs([]string{ 180 "--no-install", 181 "eslint", 182 ".", 183 "--ext", ".js,.jsx,.ts,.tsx", 184 "-c", ".pipeline/.eslintrc.json", 185 "-f", outputFormat, 186 "--ignore-pattern", ".eslintrc.js", 187 }, "./%s", outputFileName) 188 189 err = execRunner.RunExecutable("npx", args...) 190 if err != nil { 191 if failOnError { 192 return fmt.Errorf("lint execution failed. This might be the result of severe linting findings. The lint configuration used can be found here: %s", generalPurposeLintConfigURI) 193 } 194 } 195 } 196 return nil 197 } 198 199 func findEslintConfigs(utils lintUtils) []string { 200 unfilteredListOfEslintConfigs, err := utils.Glob("**/.eslintrc*") 201 if err != nil { 202 log.Entry().Warnf("Error during resolving lint config files: %v", err) 203 } 204 var eslintConfigs []string 205 206 for _, config := range unfilteredListOfEslintConfigs { 207 if strings.Contains(config, "node_modules") { 208 continue 209 } 210 211 if strings.HasPrefix(config, ".pipeline"+string(os.PathSeparator)) { 212 continue 213 } 214 215 eslintConfigs = append(eslintConfigs, config) 216 log.Entry().Info("Discovered ESLint config " + config) 217 } 218 return eslintConfigs 219 } 220 221 func prepareArgs(defaultArgs []string, outputFileNamePattern, outputFileName string) []string { 222 if outputFileName != "" { // in this case we omit the -o flag and output will go to the log 223 defaultArgs = append(defaultArgs, "-o", fmt.Sprintf(outputFileNamePattern, outputFileName)) 224 } 225 return defaultArgs 226 227 }