• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 12392579821

18 Dec 2024 12:05PM UTC coverage: 88.981% (+2.9%) from 86.121%
12392579821

push

github

thschmitt
Add support to analyze studio projects

211 of 270 new or added lines in 8 files covered. (78.15%)

40 existing lines in 4 files now uncovered.

4813 of 5409 relevant lines covered (88.98%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.29
/plugin/studio/package_analyze_command.go
1
package studio
2

3
import (
4
        "bufio"
5
        "bytes"
6
        "crypto/rand"
7
        "encoding/json"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "math"
12
        "math/big"
13
        "os"
14
        "path/filepath"
15
        "strings"
16
        "sync"
17
        "time"
18

19
        "github.com/UiPath/uipathcli/log"
20
        "github.com/UiPath/uipathcli/output"
21
        "github.com/UiPath/uipathcli/plugin"
22
        "github.com/UiPath/uipathcli/utils"
23
)
24

25
// The PackageAnalyzeCommand runs static code analyis on the project to detect common errors.
26
type PackageAnalyzeCommand struct {
27
        Exec utils.ExecProcess
28
}
29

30
func (c PackageAnalyzeCommand) Command() plugin.Command {
1✔
31
        return *plugin.NewCommand("studio").
1✔
32
                WithCategory("package", "Package", "UiPath Studio package-related actions").
1✔
33
                WithOperation("analyze", "Analyze Project", "Runs static code analysis on the project to detect common errors").
1✔
34
                WithParameter("source", plugin.ParameterTypeString, "Path to a project.json file or a folder containing project.json file", true).
1✔
35
                WithParameter("treat-warnings-as-errors", plugin.ParameterTypeBoolean, "Treat warnings as errors", false).
1✔
36
                WithParameter("stop-on-rule-violation", plugin.ParameterTypeBoolean, "Fail when any rule is violated", false)
1✔
37
}
1✔
38

39
func (c PackageAnalyzeCommand) Execute(context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
40
        source, err := c.getSource(context)
1✔
41
        if err != nil {
1✔
NEW
42
                return err
×
NEW
43
        }
×
44
        treatWarningsAsErrors, _ := c.getBoolParameter("treat-warnings-as-errors", context.Parameters)
1✔
45
        stopOnRuleViolation, _ := c.getBoolParameter("stop-on-rule-violation", context.Parameters)
1✔
46
        exitCode, result, err := c.execute(source, treatWarningsAsErrors, stopOnRuleViolation, context.Debug, logger)
1✔
47
        if err != nil {
1✔
NEW
48
                return err
×
NEW
49
        }
×
50

51
        json, err := json.Marshal(result)
1✔
52
        if err != nil {
1✔
NEW
53
                return fmt.Errorf("analyze command failed: %v", err)
×
NEW
54
        }
×
55
        err = writer.WriteResponse(*output.NewResponseInfo(200, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(json)))
1✔
56
        if err != nil {
1✔
NEW
57
                return err
×
NEW
58
        }
×
59
        if exitCode != 0 {
1✔
NEW
60
                return errors.New("")
×
NEW
61
        }
×
62
        return nil
1✔
63
}
64

65
func (c PackageAnalyzeCommand) execute(source string, treatWarningsAsErrors bool, stopOnRuleViolation bool, debug bool, logger log.Logger) (int, *packageAnalyzeResult, error) {
1✔
66
        if !debug {
2✔
67
                bar := c.newAnalyzingProgressBar(logger)
1✔
68
                defer close(bar)
1✔
69
        }
1✔
70

71
        jsonResultFilePath, err := c.getTemporaryJsonResultFilePath()
1✔
72
        if err != nil {
1✔
NEW
73
                return 1, nil, err
×
NEW
74
        }
×
75
        defer os.Remove(jsonResultFilePath)
1✔
76

1✔
77
        args := []string{"package", "analyze", source, "--resultPath", jsonResultFilePath}
1✔
78
        if treatWarningsAsErrors {
1✔
NEW
79
                args = append(args, "--treatWarningsAsErrors")
×
NEW
80
        }
×
81
        if stopOnRuleViolation {
1✔
NEW
82
                args = append(args, "--stopOnRuleViolation")
×
NEW
83
        }
×
84

85
        uipcli := newUipcli(c.Exec, logger)
1✔
86
        cmd, err := uipcli.Execute(args...)
1✔
87
        if err != nil {
1✔
NEW
88
                return 1, nil, err
×
NEW
89
        }
×
90
        stdout, err := cmd.StdoutPipe()
1✔
91
        if err != nil {
1✔
NEW
92
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
NEW
93
        }
×
94
        defer stdout.Close()
1✔
95
        stderr, err := cmd.StderrPipe()
1✔
96
        if err != nil {
1✔
NEW
97
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
NEW
98
        }
×
99
        defer stderr.Close()
1✔
100
        err = cmd.Start()
1✔
101
        if err != nil {
1✔
NEW
102
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
NEW
103
        }
×
104

105
        stderrOutputBuilder := new(strings.Builder)
1✔
106
        stderrReader := io.TeeReader(stderr, stderrOutputBuilder)
1✔
107

1✔
108
        var wg sync.WaitGroup
1✔
109
        wg.Add(3)
1✔
110
        go c.readOutput(stdout, logger, &wg)
1✔
111
        go c.readOutput(stderrReader, logger, &wg)
1✔
112
        go c.wait(cmd, &wg)
1✔
113
        wg.Wait()
1✔
114

1✔
115
        violations, err := c.readAnalyzeResult(jsonResultFilePath)
1✔
116
        if err != nil {
1✔
NEW
117
                return 1, nil, err
×
NEW
118
        }
×
119

120
        exitCode := cmd.ExitCode()
1✔
121
        var result *packageAnalyzeResult
1✔
122
        if exitCode == 0 {
2✔
123
                result = newSucceededPackageAnalyzeResult(violations)
1✔
124
        } else {
1✔
NEW
125
                result = newFailedPackageAnalyzeResult(
×
NEW
126
                        violations,
×
NEW
127
                        stderrOutputBuilder.String(),
×
NEW
128
                )
×
NEW
129
        }
×
130
        return exitCode, result, nil
1✔
131
}
132

133
func (c PackageAnalyzeCommand) getTemporaryJsonResultFilePath() (string, error) {
1✔
134
        tempDirectory, err := utils.Directories{}.Temp()
1✔
135
        if err != nil {
1✔
NEW
136
                return "", err
×
NEW
137
        }
×
138
        fileName := c.randomJsonResultFileName()
1✔
139
        return filepath.Join(tempDirectory, fileName), nil
1✔
140
}
141

142
func (c PackageAnalyzeCommand) randomJsonResultFileName() string {
1✔
143
        value, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
1✔
144
        return "analyzeresult-" + value.String() + ".json"
1✔
145
}
1✔
146

147
func (c PackageAnalyzeCommand) readAnalyzeResult(path string) ([]packageAnalyzeViolation, error) {
1✔
148
        file, err := os.Open(path)
1✔
149
        if err != nil {
1✔
NEW
150
                return []packageAnalyzeViolation{}, fmt.Errorf("Error reading %s file: %v", filepath.Base(path), err)
×
NEW
151
        }
×
152
        defer file.Close()
1✔
153
        byteValue, err := io.ReadAll(file)
1✔
154
        if err != nil {
1✔
NEW
155
                return []packageAnalyzeViolation{}, fmt.Errorf("Error reading %s file: %v", filepath.Base(path), err)
×
NEW
156
        }
×
157

158
        var result analyzeResultJson
1✔
159
        err = json.Unmarshal(byteValue, &result)
1✔
160
        if err != nil {
1✔
NEW
161
                return []packageAnalyzeViolation{}, fmt.Errorf("Error parsing %s file: %v", filepath.Base(path), err)
×
NEW
162
        }
×
163
        return c.convertToViolations(result), nil
1✔
164
}
165

166
func (c PackageAnalyzeCommand) convertToViolations(json analyzeResultJson) []packageAnalyzeViolation {
1✔
167
        violations := []packageAnalyzeViolation{}
1✔
168
        for _, entry := range json {
2✔
169
                var activityId *packageAnalyzeActivityId
1✔
170
                if entry.ActivityId != nil {
2✔
171
                        activityId = &packageAnalyzeActivityId{
1✔
172
                                Id:    entry.ActivityId.Id,
1✔
173
                                IdRef: entry.ActivityId.IdRef,
1✔
174
                        }
1✔
175
                }
1✔
176
                var item *packageAnalyzeItem
1✔
177
                if entry.Item != nil {
2✔
178
                        item = &packageAnalyzeItem{
1✔
179
                                Name: entry.Item.Name,
1✔
180
                                Type: entry.Item.Type,
1✔
181
                        }
1✔
182
                }
1✔
183
                violation := packageAnalyzeViolation{
1✔
184
                        ErrorCode:           entry.ErrorCode,
1✔
185
                        Description:         entry.Description,
1✔
186
                        RuleName:            entry.RuleName,
1✔
187
                        FilePath:            entry.FilePath,
1✔
188
                        ActivityDisplayName: entry.ActivityDisplayName,
1✔
189
                        WorkflowDisplayName: entry.WorkflowDisplayName,
1✔
190
                        ErrorSeverity:       entry.ErrorSeverity,
1✔
191
                        Recommendation:      entry.Recommendation,
1✔
192
                        DocumentationLink:   entry.DocumentationLink,
1✔
193
                        ActivityId:          activityId,
1✔
194
                        Item:                item,
1✔
195
                }
1✔
196
                violations = append(violations, violation)
1✔
197
        }
198
        return violations
1✔
199
}
200

201
func (c PackageAnalyzeCommand) wait(cmd utils.ExecCmd, wg *sync.WaitGroup) {
1✔
202
        defer wg.Done()
1✔
203
        _ = cmd.Wait()
1✔
204
}
1✔
205

206
func (c PackageAnalyzeCommand) newAnalyzingProgressBar(logger log.Logger) chan struct{} {
1✔
207
        progressBar := utils.NewProgressBar(logger)
1✔
208
        ticker := time.NewTicker(10 * time.Millisecond)
1✔
209
        cancel := make(chan struct{})
1✔
210
        go func() {
2✔
211
                for {
2✔
212
                        select {
1✔
213
                        case <-ticker.C:
1✔
214
                                progressBar.Tick("analyzing...  ")
1✔
215
                        case <-cancel:
1✔
216
                                ticker.Stop()
1✔
217
                                progressBar.Remove()
1✔
218
                                return
1✔
219
                        }
220
                }
221
        }()
222
        return cancel
1✔
223
}
224

225
func (c PackageAnalyzeCommand) getSource(context plugin.ExecutionContext) (string, error) {
1✔
226
        source, _ := c.getParameter("source", context.Parameters)
1✔
227
        if source == "" {
1✔
NEW
228
                return "", errors.New("source is not set")
×
NEW
229
        }
×
230
        fileInfo, err := os.Stat(source)
1✔
231
        if err != nil {
1✔
NEW
232
                return "", fmt.Errorf("%s not found", defaultProjectJson)
×
NEW
233
        }
×
234
        if fileInfo.IsDir() {
2✔
235
                source = filepath.Join(source, defaultProjectJson)
1✔
236
        }
1✔
237
        return source, nil
1✔
238
}
239

240
func (c PackageAnalyzeCommand) readOutput(output io.Reader, logger log.Logger, wg *sync.WaitGroup) {
1✔
241
        defer wg.Done()
1✔
242
        scanner := bufio.NewScanner(output)
1✔
243
        scanner.Split(bufio.ScanRunes)
1✔
244
        for scanner.Scan() {
2✔
245
                logger.Log(scanner.Text())
1✔
246
        }
1✔
247
}
248

249
func (c PackageAnalyzeCommand) getParameter(name string, parameters []plugin.ExecutionParameter) (string, error) {
1✔
250
        for _, p := range parameters {
2✔
251
                if p.Name == name {
2✔
252
                        if data, ok := p.Value.(string); ok {
2✔
253
                                return data, nil
1✔
254
                        }
1✔
255
                }
256
        }
NEW
257
        return "", fmt.Errorf("Could not find '%s' parameter", name)
×
258
}
259

260
func (c PackageAnalyzeCommand) getBoolParameter(name string, parameters []plugin.ExecutionParameter) (bool, error) {
1✔
261
        for _, p := range parameters {
2✔
262
                if p.Name == name {
1✔
NEW
263
                        if data, ok := p.Value.(bool); ok {
×
NEW
264
                                return data, nil
×
NEW
265
                        }
×
266
                }
267
        }
268
        return false, fmt.Errorf("Could not find '%s' parameter", name)
1✔
269
}
270

271
func NewPackageAnalyzeCommand() *PackageAnalyzeCommand {
1✔
272
        return &PackageAnalyzeCommand{utils.NewExecProcess()}
1✔
273
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc