github.com/xgoffin/jenkins-library@v1.154.0/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  }