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

UiPath / uipathcli / 13244621400

10 Feb 2025 03:39PM UTC coverage: 90.38% (-0.07%) from 90.449%
13244621400

push

github

web-flow
Merge pull request #144 from UiPath/fix/studio-package-progressbar

Show progress bar for downloading plugin tools

26 of 33 new or added lines in 3 files covered. (78.79%)

3 existing lines in 2 files now uncovered.

4989 of 5520 relevant lines covered (90.38%)

1.01 hits per line

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

84.82
/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/directories"
23
        "github.com/UiPath/uipathcli/utils/process"
24
        "github.com/UiPath/uipathcli/utils/visualization"
25
)
26

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

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

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

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

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

1✔
74
        args := []string{"package", "analyze", source, "--resultPath", jsonResultFilePath}
1✔
75
        if treatWarningsAsErrors {
2✔
76
                args = append(args, "--treatWarningsAsErrors")
1✔
77
        }
1✔
78
        if stopOnRuleViolation {
2✔
79
                args = append(args, "--stopOnRuleViolation")
1✔
80
        }
1✔
81

82
        projectReader := newStudioProjectReader(source)
1✔
83

1✔
84
        uipcli := newUipcli(c.Exec, logger)
1✔
85
        err = uipcli.Initialize(projectReader.GetTargetFramework())
1✔
86
        if err != nil {
1✔
NEW
87
                return 1, nil, err
×
NEW
88
        }
×
89

90
        if !debug {
2✔
91
                bar := c.newAnalyzingProgressBar(logger)
1✔
92
                defer close(bar)
1✔
93
        }
1✔
94
        cmd, err := uipcli.Execute(args...)
1✔
95
        if err != nil {
1✔
96
                return 1, nil, err
×
97
        }
×
98
        stdout, err := cmd.StdoutPipe()
1✔
99
        if err != nil {
1✔
100
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
101
        }
×
102
        defer stdout.Close()
1✔
103
        stderr, err := cmd.StderrPipe()
1✔
104
        if err != nil {
1✔
105
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
106
        }
×
107
        defer stderr.Close()
1✔
108
        err = cmd.Start()
1✔
109
        if err != nil {
1✔
110
                return 1, nil, fmt.Errorf("Could not run analyze command: %v", err)
×
111
        }
×
112

113
        stderrOutputBuilder := new(strings.Builder)
1✔
114
        stderrReader := io.TeeReader(stderr, stderrOutputBuilder)
1✔
115

1✔
116
        var wg sync.WaitGroup
1✔
117
        wg.Add(3)
1✔
118
        go c.readOutput(stdout, logger, &wg)
1✔
119
        go c.readOutput(stderrReader, logger, &wg)
1✔
120
        go c.wait(cmd, &wg)
1✔
121
        wg.Wait()
1✔
122

1✔
123
        violations, err := c.readAnalyzeResult(jsonResultFilePath)
1✔
124
        if err != nil {
1✔
125
                return 1, nil, err
×
126
        }
×
127

128
        exitCode := cmd.ExitCode()
1✔
129
        var result *packageAnalyzeResult
1✔
130
        if exitCode == 0 {
2✔
131
                result = newSucceededPackageAnalyzeResult(violations)
1✔
132
        } else {
2✔
133
                result = newFailedPackageAnalyzeResult(
1✔
134
                        violations,
1✔
135
                        stderrOutputBuilder.String(),
1✔
136
                )
1✔
137
        }
1✔
138
        return exitCode, result, nil
1✔
139
}
140

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

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

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

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

177
func (c PackageAnalyzeCommand) convertToViolations(json analyzeResultJson) []packageAnalyzeViolation {
1✔
178
        violations := []packageAnalyzeViolation{}
1✔
179
        for _, entry := range json {
2✔
180
                var activityId *packageAnalyzeActivityId
1✔
181
                if entry.ActivityId != nil {
2✔
182
                        activityId = &packageAnalyzeActivityId{
1✔
183
                                Id:    entry.ActivityId.Id,
1✔
184
                                IdRef: entry.ActivityId.IdRef,
1✔
185
                        }
1✔
186
                }
1✔
187
                var item *packageAnalyzeItem
1✔
188
                if entry.Item != nil {
2✔
189
                        item = &packageAnalyzeItem{
1✔
190
                                Name: entry.Item.Name,
1✔
191
                                Type: entry.Item.Type,
1✔
192
                        }
1✔
193
                }
1✔
194
                violation := packageAnalyzeViolation{
1✔
195
                        ErrorCode:           entry.ErrorCode,
1✔
196
                        Description:         entry.Description,
1✔
197
                        RuleName:            entry.RuleName,
1✔
198
                        FilePath:            entry.FilePath,
1✔
199
                        ActivityDisplayName: entry.ActivityDisplayName,
1✔
200
                        WorkflowDisplayName: entry.WorkflowDisplayName,
1✔
201
                        ErrorSeverity:       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) wait(cmd process.ExecCmd, wg *sync.WaitGroup) {
1✔
213
        defer wg.Done()
1✔
214
        _ = cmd.Wait()
1✔
215
}
1✔
216

217
func (c PackageAnalyzeCommand) newAnalyzingProgressBar(logger log.Logger) chan struct{} {
1✔
218
        progressBar := visualization.NewProgressBar(logger)
1✔
219
        ticker := time.NewTicker(10 * time.Millisecond)
1✔
220
        cancel := make(chan struct{})
1✔
221
        var percent float64 = 0
1✔
222
        go func() {
2✔
223
                for {
2✔
224
                        select {
1✔
225
                        case <-ticker.C:
1✔
226
                                progressBar.UpdatePercentage("analyzing...  ", percent)
1✔
227
                                percent = percent + 1
1✔
228
                                if percent > 100 {
2✔
229
                                        percent = 0
1✔
230
                                }
1✔
231
                        case <-cancel:
1✔
232
                                ticker.Stop()
1✔
233
                                progressBar.Remove()
1✔
234
                                return
1✔
235
                        }
236
                }
237
        }()
238
        return cancel
1✔
239
}
240

241
func (c PackageAnalyzeCommand) getSource(context plugin.ExecutionContext) (string, error) {
1✔
242
        source := c.getParameter("source", context.Parameters)
1✔
243
        if source == "" {
1✔
244
                return "", errors.New("source is not set")
×
245
        }
×
246
        source, _ = filepath.Abs(source)
1✔
247
        fileInfo, err := os.Stat(source)
1✔
248
        if err != nil {
1✔
249
                return "", fmt.Errorf("%s not found", defaultProjectJson)
×
250
        }
×
251
        if fileInfo.IsDir() {
2✔
252
                source = filepath.Join(source, defaultProjectJson)
1✔
253
        }
1✔
254
        return source, nil
1✔
255
}
256

257
func (c PackageAnalyzeCommand) readOutput(output io.Reader, logger log.Logger, wg *sync.WaitGroup) {
1✔
258
        defer wg.Done()
1✔
259
        scanner := bufio.NewScanner(output)
1✔
260
        scanner.Split(bufio.ScanRunes)
1✔
261
        for scanner.Scan() {
2✔
262
                logger.Log(scanner.Text())
1✔
263
        }
1✔
264
}
265

266
func (c PackageAnalyzeCommand) getParameter(name string, parameters []plugin.ExecutionParameter) string {
1✔
267
        result := ""
1✔
268
        for _, p := range parameters {
2✔
269
                if p.Name == name {
2✔
270
                        if data, ok := p.Value.(string); ok {
2✔
271
                                result = data
1✔
272
                                break
1✔
273
                        }
274
                }
275
        }
276
        return result
1✔
277
}
278

279
func (c PackageAnalyzeCommand) getBoolParameter(name string, parameters []plugin.ExecutionParameter) bool {
1✔
280
        result := false
1✔
281
        for _, p := range parameters {
2✔
282
                if p.Name == name {
2✔
283
                        if data, ok := p.Value.(bool); ok {
2✔
284
                                result = data
1✔
285
                                break
1✔
286
                        }
287
                }
288
        }
289
        return result
1✔
290
}
291

292
func NewPackageAnalyzeCommand() *PackageAnalyzeCommand {
1✔
293
        return &PackageAnalyzeCommand{process.NewExecProcess()}
1✔
294
}
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