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

UiPath / uipathcli / 14448614326

14 Apr 2025 02:45PM UTC coverage: 90.072% (+0.2%) from 89.92%
14448614326

push

github

thschmitt
Add project builder for generating UiPath Studio projects

Added `ProjectBuilder` to generate new UiPath Studio projects on every
test case execution so that tests do not share any state and UiPath
Studio projects can be easily customized.

Moved file operation methods into `setup.go` and reduced duplication
across multiple plugin tests.

6514 of 7232 relevant lines covered (90.07%)

1.01 hits per line

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

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

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

16
        "github.com/UiPath/uipathcli/log"
17
        "github.com/UiPath/uipathcli/output"
18
        "github.com/UiPath/uipathcli/plugin"
19
        "github.com/UiPath/uipathcli/utils/directories"
20
        "github.com/UiPath/uipathcli/utils/process"
21
        "github.com/UiPath/uipathcli/utils/visualization"
22
)
23

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

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

39
func (c PackageAnalyzeCommand) Execute(ctx plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
40
        source, err := c.getSource(ctx)
1✔
41
        if err != nil {
2✔
42
                return err
1✔
43
        }
1✔
44

45
        stopOnRuleViolation := c.getBoolParameter("stop-on-rule-violation", true, ctx.Parameters)
1✔
46
        treatWarningsAsErrors := c.getBoolParameter("treat-warnings-as-errors", false, ctx.Parameters)
1✔
47
        governanceFile, err := c.getGovernanceFile(ctx, source)
1✔
48
        if err != nil {
2✔
49
                return err
1✔
50
        }
1✔
51

52
        exitCode, result, err := c.execute(source, stopOnRuleViolation, treatWarningsAsErrors, governanceFile, ctx.Debug, logger)
1✔
53
        if err != nil {
2✔
54
                return err
1✔
55
        }
1✔
56

57
        json, err := json.Marshal(result)
1✔
58
        if err != nil {
1✔
59
                return fmt.Errorf("analyze command failed: %v", err)
×
60
        }
×
61
        err = writer.WriteResponse(*output.NewResponseInfo(200, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(json)))
1✔
62
        if err != nil {
1✔
63
                return err
×
64
        }
×
65
        if exitCode != 0 {
2✔
66
                return errors.New("")
1✔
67
        }
1✔
68
        return nil
1✔
69
}
70

71
func (c PackageAnalyzeCommand) execute(source string, stopOnRuleViolation bool, treatWarningsAsErrors bool, governanceFile string, debug bool, logger log.Logger) (int, *packageAnalyzeResult, error) {
1✔
72
        jsonResultFilePath, err := c.getTemporaryJsonResultFilePath()
1✔
73
        if err != nil {
1✔
74
                return 1, nil, err
×
75
        }
×
76
        defer os.Remove(jsonResultFilePath)
1✔
77

1✔
78
        projectReader := newStudioProjectReader(source)
1✔
79
        project, err := projectReader.ReadMetadata()
1✔
80
        if err != nil {
1✔
81
                return 1, nil, err
×
82
        }
×
83
        supported, err := project.TargetFramework.IsSupported()
1✔
84
        if !supported {
2✔
85
                return 1, nil, err
1✔
86
        }
1✔
87

88
        uipcli := newUipcli(c.Exec, logger)
1✔
89
        err = uipcli.Initialize(project.TargetFramework)
1✔
90
        if err != nil {
1✔
91
                return 1, nil, err
×
92
        }
×
93

94
        if !debug {
2✔
95
                bar := c.newAnalyzingProgressBar(logger)
1✔
96
                defer close(bar)
1✔
97
        }
1✔
98
        args := c.prepareAnalyzeArguments(source, jsonResultFilePath, governanceFile)
1✔
99
        exitCode, stdErr, err := uipcli.ExecuteAndWait(args...)
1✔
100
        if err != nil {
1✔
101
                return exitCode, nil, err
×
102
        }
×
103

104
        violations, err := c.readAnalyzeResult(jsonResultFilePath)
1✔
105
        if err != nil {
1✔
106
                return 1, nil, err
×
107
        }
×
108
        errorViolationsFound := c.hasErrorViolations(violations, treatWarningsAsErrors)
1✔
109

1✔
110
        if exitCode != 0 {
2✔
111
                return exitCode, newErrorPackageAnalyzeResult(violations, stdErr), nil
1✔
112
        } else if stopOnRuleViolation && errorViolationsFound {
3✔
113
                return 1, newFailedPackageAnalyzeResult(violations), nil
1✔
114
        } else if errorViolationsFound {
3✔
115
                return 0, newFailedPackageAnalyzeResult(violations), nil
1✔
116
        }
1✔
117
        return 0, newSucceededPackageAnalyzeResult(violations), nil
1✔
118
}
119

120
func (c PackageAnalyzeCommand) prepareAnalyzeArguments(source string, jsonResultFilePath string, governanceFile string) []string {
1✔
121
        args := []string{"package", "analyze", source, "--resultPath", jsonResultFilePath}
1✔
122
        if governanceFile != "" {
2✔
123
                args = append(args, "--governanceFilePath", governanceFile)
1✔
124
        }
1✔
125
        return args
1✔
126
}
127

128
func (c PackageAnalyzeCommand) hasErrorViolations(violations []packageAnalyzeViolation, treatWarningsAsErrors bool) bool {
1✔
129
        for _, violation := range violations {
2✔
130
                if violation.Severity == "Error" {
2✔
131
                        return true
1✔
132
                }
1✔
133
                if treatWarningsAsErrors && violation.Severity == "Warning" {
1✔
134
                        return true
×
135
                }
×
136
        }
137
        return false
1✔
138
}
139

140
func (c PackageAnalyzeCommand) getTemporaryJsonResultFilePath() (string, error) {
1✔
141
        tempDirectory, err := directories.Temp()
1✔
142
        if err != nil {
1✔
143
                return "", err
×
144
        }
×
145
        fileName := c.randomJsonResultFileName()
1✔
146
        return filepath.Join(tempDirectory, fileName), nil
1✔
147
}
148

149
func (c PackageAnalyzeCommand) randomJsonResultFileName() string {
1✔
150
        value, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
1✔
151
        return "analyzeresult-" + value.String() + ".json"
1✔
152
}
1✔
153

154
func (c PackageAnalyzeCommand) readAnalyzeResult(path string) ([]packageAnalyzeViolation, error) {
1✔
155
        file, err := os.Open(path)
1✔
156
        if err != nil && errors.Is(err, os.ErrNotExist) {
2✔
157
                return []packageAnalyzeViolation{}, nil
1✔
158
        }
1✔
159
        if err != nil {
1✔
160
                return []packageAnalyzeViolation{}, fmt.Errorf("Error reading %s file: %v", filepath.Base(path), err)
×
161
        }
×
162
        defer file.Close()
1✔
163
        byteValue, err := io.ReadAll(file)
1✔
164
        if err != nil {
1✔
165
                return []packageAnalyzeViolation{}, fmt.Errorf("Error reading %s file: %v", filepath.Base(path), err)
×
166
        }
×
167

168
        var result analyzeResultJson
1✔
169
        err = json.Unmarshal(byteValue, &result)
1✔
170
        if err != nil {
1✔
171
                return []packageAnalyzeViolation{}, fmt.Errorf("Error parsing %s file: %v", filepath.Base(path), err)
×
172
        }
×
173
        return c.convertToViolations(result), nil
1✔
174
}
175

176
func (c PackageAnalyzeCommand) convertToViolations(json analyzeResultJson) []packageAnalyzeViolation {
1✔
177
        violations := []packageAnalyzeViolation{}
1✔
178
        for _, entry := range json {
2✔
179
                var activityId *packageAnalyzeActivityId
1✔
180
                if entry.ActivityId != nil {
2✔
181
                        activityId = &packageAnalyzeActivityId{
1✔
182
                                Id:    entry.ActivityId.Id,
1✔
183
                                IdRef: entry.ActivityId.IdRef,
1✔
184
                        }
1✔
185
                }
1✔
186
                var item *packageAnalyzeItem
1✔
187
                if entry.Item != nil {
2✔
188
                        item = &packageAnalyzeItem{
1✔
189
                                Name: entry.Item.Name,
1✔
190
                                Type: entry.Item.Type,
1✔
191
                        }
1✔
192
                }
1✔
193
                violation := packageAnalyzeViolation{
1✔
194
                        ErrorCode:           entry.ErrorCode,
1✔
195
                        Description:         entry.Description,
1✔
196
                        RuleName:            entry.RuleName,
1✔
197
                        FilePath:            entry.FilePath,
1✔
198
                        ActivityDisplayName: entry.ActivityDisplayName,
1✔
199
                        WorkflowDisplayName: entry.WorkflowDisplayName,
1✔
200
                        ErrorSeverity:       entry.ErrorSeverity,
1✔
201
                        Severity:            c.convertToSeverity(entry.ErrorSeverity),
1✔
202
                        Recommendation:      entry.Recommendation,
1✔
203
                        DocumentationLink:   entry.DocumentationLink,
1✔
204
                        ActivityId:          activityId,
1✔
205
                        Item:                item,
1✔
206
                }
1✔
207
                violations = append(violations, violation)
1✔
208
        }
209
        return violations
1✔
210
}
211

212
func (c PackageAnalyzeCommand) convertToSeverity(errorSeverity int) string {
1✔
213
        switch errorSeverity {
1✔
214
        case 0:
×
215
                return "Off"
×
216
        case 1:
1✔
217
                return "Error"
1✔
218
        case 2:
1✔
219
                return "Warning"
1✔
220
        case 3:
1✔
221
                return "Info"
1✔
222
        default:
×
223
                return "Trace"
×
224
        }
225
}
226

227
func (c PackageAnalyzeCommand) newAnalyzingProgressBar(logger log.Logger) chan struct{} {
1✔
228
        progressBar := visualization.NewProgressBar(logger)
1✔
229
        ticker := time.NewTicker(10 * time.Millisecond)
1✔
230
        cancel := make(chan struct{})
1✔
231
        var percent float64 = 0
1✔
232
        go func() {
2✔
233
                for {
2✔
234
                        select {
1✔
235
                        case <-ticker.C:
1✔
236
                                progressBar.UpdatePercentage("analyzing...  ", percent)
1✔
237
                                percent = percent + 1
1✔
238
                                if percent > 100 {
2✔
239
                                        percent = 0
1✔
240
                                }
1✔
241
                        case <-cancel:
1✔
242
                                ticker.Stop()
1✔
243
                                progressBar.Remove()
1✔
244
                                return
1✔
245
                        }
246
                }
247
        }()
248
        return cancel
1✔
249
}
250

251
func (c PackageAnalyzeCommand) getSource(ctx plugin.ExecutionContext) (string, error) {
1✔
252
        source := c.getParameter("source", ".", ctx.Parameters)
1✔
253
        source, _ = filepath.Abs(source)
1✔
254
        fileInfo, err := os.Stat(source)
1✔
255
        if err != nil {
2✔
256
                return "", fmt.Errorf("%s not found", defaultProjectJson)
1✔
257
        }
1✔
258
        if fileInfo.IsDir() {
2✔
259
                source = filepath.Join(source, defaultProjectJson)
1✔
260
        }
1✔
261
        return source, nil
1✔
262
}
263

264
func (c PackageAnalyzeCommand) defaultGovernanceFile(source string) string {
1✔
265
        directory := filepath.Dir(source)
1✔
266
        file := filepath.Join(directory, "uipath.policy.default.json")
1✔
267
        fileInfo, err := os.Stat(file)
1✔
268
        if err != nil || fileInfo.IsDir() {
2✔
269
                return ""
1✔
270
        }
1✔
271
        return file
1✔
272
}
273

274
func (c PackageAnalyzeCommand) getGovernanceFile(context plugin.ExecutionContext, source string) (string, error) {
1✔
275
        governanceFileParam := c.getParameter("governance-file", "", context.Parameters)
1✔
276
        if governanceFileParam == "" {
2✔
277
                return c.defaultGovernanceFile(source), nil
1✔
278
        }
1✔
279

280
        file, _ := filepath.Abs(governanceFileParam)
1✔
281
        fileInfo, err := os.Stat(file)
1✔
282
        if err != nil || fileInfo.IsDir() {
2✔
283
                return "", fmt.Errorf("%s not found", governanceFileParam)
1✔
284
        }
1✔
285
        return file, nil
1✔
286
}
287

288
func (c PackageAnalyzeCommand) getParameter(name string, defaultValue string, parameters []plugin.ExecutionParameter) string {
1✔
289
        result := defaultValue
1✔
290
        for _, p := range parameters {
2✔
291
                if p.Name == name {
2✔
292
                        if data, ok := p.Value.(string); ok {
2✔
293
                                result = data
1✔
294
                                break
1✔
295
                        }
296
                }
297
        }
298
        return result
1✔
299
}
300

301
func (c PackageAnalyzeCommand) getBoolParameter(name string, defaultValue bool, parameters []plugin.ExecutionParameter) bool {
1✔
302
        result := defaultValue
1✔
303
        for _, p := range parameters {
2✔
304
                if p.Name == name {
2✔
305
                        if data, ok := p.Value.(bool); ok {
2✔
306
                                result = data
1✔
307
                                break
1✔
308
                        }
309
                }
310
        }
311
        return result
1✔
312
}
313

314
func NewPackageAnalyzeCommand() *PackageAnalyzeCommand {
1✔
315
        return &PackageAnalyzeCommand{process.NewExecProcess()}
1✔
316
}
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

© 2026 Coveralls, Inc