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

UiPath / uipathcli / 13244383932

10 Feb 2025 03:26PM UTC coverage: 90.417% (-0.03%) from 90.449%
13244383932

Pull #144

github

thschmitt
Show progress bar for downloading plugin tools

The downloading and packaging progress bars were overlapping during the
initialization of the external plugin. Split up the initialization from
the execution to show a separate progress bar for each operation.
Pull Request #144: Show progress bar for downloading plugin tools

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

1 existing line in 1 file now uncovered.

4991 of 5520 relevant lines covered (90.42%)

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