/
npmExecuteLint.go
227 lines (192 loc) · 7.02 KB
/
npmExecuteLint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package cmd
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/npm"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
)
type lintUtils interface {
Glob(pattern string) (matches []string, err error)
getExecRunner() command.ExecRunner
getGeneralPurposeConfig(configURL string)
}
type lintUtilsBundle struct {
*piperutils.Files
execRunner *command.Command
client *piperhttp.Client
}
func newLintUtilsBundle() *lintUtilsBundle {
return &lintUtilsBundle{
Files: &piperutils.Files{},
client: &piperhttp.Client{},
}
}
func (u *lintUtilsBundle) getExecRunner() command.ExecRunner {
if u.execRunner == nil {
u.execRunner = &command.Command{}
u.execRunner.Stdout(log.Writer())
u.execRunner.Stderr(log.Writer())
}
return u.execRunner
}
func (u *lintUtilsBundle) getGeneralPurposeConfig(configURL string) {
response, err := u.client.SendRequest(http.MethodGet, configURL, nil, nil, nil)
if err != nil {
log.Entry().Warnf("failed to download general purpose configuration: %v", err)
return
}
defer response.Body.Close()
content, err := io.ReadAll(response.Body)
if err != nil {
log.Entry().Warnf("error while reading the general purpose configuration: %v", err)
return
}
err = u.FileWrite(filepath.Join(".pipeline", ".eslintrc.json"), content, os.ModePerm)
if err != nil {
log.Entry().Warnf("failed to write .eslintrc.json file to .pipeline/: %v", err)
}
}
func npmExecuteLint(config npmExecuteLintOptions, telemetryData *telemetry.CustomData) {
utils := newLintUtilsBundle()
npmExecutorOptions := npm.ExecutorOptions{DefaultNpmRegistry: config.DefaultNpmRegistry, ExecRunner: utils.getExecRunner()}
npmExecutor := npm.NewExecutor(npmExecutorOptions)
err := runNpmExecuteLint(npmExecutor, utils, &config)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runNpmExecuteLint(npmExecutor npm.Executor, utils lintUtils, config *npmExecuteLintOptions) error {
if len(config.RunScript) == 0 {
return fmt.Errorf("runScript is not allowed to be empty!")
}
packageJSONFiles := npmExecutor.FindPackageJSONFiles()
packagesWithLintScript, _ := npmExecutor.FindPackageJSONFilesWithScript(packageJSONFiles, config.RunScript)
if len(packagesWithLintScript) > 0 {
if config.Install {
err := npmExecutor.InstallAllDependencies(packagesWithLintScript)
if err != nil {
return err
}
}
err := runLintScript(npmExecutor, config.RunScript, config.FailOnError)
if err != nil {
return err
}
} else {
if config.Install {
err := npmExecutor.InstallAllDependencies(packageJSONFiles)
if err != nil {
return err
}
}
err := runDefaultLint(npmExecutor, utils, config.FailOnError, config.OutputFormat, config.OutputFileName)
if err != nil {
return err
}
}
return nil
}
func runLintScript(npmExecutor npm.Executor, runScript string, failOnError bool) error {
runScripts := []string{runScript}
runOptions := []string{"--silent"}
err := npmExecutor.RunScriptsInAllPackages(runScripts, runOptions, nil, false, nil, nil)
if err != nil {
if failOnError {
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)
}
}
return nil
}
func runDefaultLint(npmExecutor npm.Executor, utils lintUtils, failOnError bool, outputFormat string, outputFileName string) error {
execRunner := utils.getExecRunner()
eslintConfigs := findEslintConfigs(utils)
err := npmExecutor.SetNpmRegistries()
if err != nil {
log.Entry().Warnf("failed to set npm registries before running default lint: %v", err)
}
// 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,
// i.e., .jsx, .ts, .tsx, since we can not be sure that the provided config enables parsing of these file types.
if len(eslintConfigs) > 0 {
for i, config := range eslintConfigs {
lintPattern := "."
dir := filepath.Dir(config)
if dir != "." {
lintPattern = dir + "/**/*.js"
}
args := prepareArgs([]string{
"eslint",
lintPattern,
"-f", outputFormat,
"--ignore-pattern", "node_modules/",
"--ignore-pattern", ".eslintrc.js",
}, fmt.Sprintf("./%s_%%s", strconv.Itoa(i)), outputFileName)
err = execRunner.RunExecutable("npx", args...)
if err != nil {
if failOnError {
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")
}
}
}
} else {
// install dependencies manually, since npx cannot resolve the dependencies required for general purpose
// ESLint config, e.g., TypeScript ESLint plugin
log.Entry().Info("Run ESLint with general purpose config")
generalPurposeLintConfigURI := "https://raw.githubusercontent.com/SAP/jenkins-library/master/resources/.eslintrc.json"
utils.getGeneralPurposeConfig(generalPurposeLintConfigURI)
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")
if err != nil {
if failOnError {
return fmt.Errorf("linter installation failed: %s", err)
}
}
args := prepareArgs([]string{
"--no-install",
"eslint",
".",
"--ext", ".js,.jsx,.ts,.tsx",
"-c", ".pipeline/.eslintrc.json",
"-f", outputFormat,
"--ignore-pattern", ".eslintrc.js",
}, "./%s", outputFileName)
err = execRunner.RunExecutable("npx", args...)
if err != nil {
if failOnError {
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)
}
}
}
return nil
}
func findEslintConfigs(utils lintUtils) []string {
unfilteredListOfEslintConfigs, err := utils.Glob("**/.eslintrc*")
if err != nil {
log.Entry().Warnf("Error during resolving lint config files: %v", err)
}
var eslintConfigs []string
for _, config := range unfilteredListOfEslintConfigs {
if strings.Contains(config, "node_modules") {
continue
}
if strings.HasPrefix(config, ".pipeline"+string(os.PathSeparator)) {
continue
}
eslintConfigs = append(eslintConfigs, config)
log.Entry().Info("Discovered ESLint config " + config)
}
return eslintConfigs
}
func prepareArgs(defaultArgs []string, outputFileNamePattern, outputFileName string) []string {
if outputFileName != "" { // in this case we omit the -o flag and output will go to the log
defaultArgs = append(defaultArgs, "-o", fmt.Sprintf(outputFileNamePattern, outputFileName))
}
return defaultArgs
}