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

UiPath / uipathcli / 22833036745

09 Mar 2026 12:08AM UTC coverage: 90.732% (-0.2%) from 90.914%
22833036745

Pull #212

github

Chibi Vikram
Fix TestWriteMultipartFormWithClosedWriter to use failing writer

Use a writer that errors on Write() instead of a closed multipart writer,
since multipart.Writer.CreatePart doesn't check closed state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request #212: Add studio solution commands, skill, and agents.md

598 of 674 new or added lines in 16 files covered. (88.72%)

3 existing lines in 1 file now uncovered.

7372 of 8125 relevant lines covered (90.73%)

1.02 hits per line

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

83.56
/plugin/studio/solution/pack/solution_pack_command.go
1
// Package pack implements the command plugin for packing a UiPath solution
2
// directory into a .uis file (ZIP archive).
3
package pack
4

5
import (
6
        "archive/zip"
7
        "bytes"
8
        "encoding/json"
9
        "fmt"
10
        "io"
11
        "net/http"
12
        "os"
13
        "path/filepath"
14
        "strings"
15

16
        "github.com/UiPath/uipathcli/log"
17
        "github.com/UiPath/uipathcli/output"
18
        "github.com/UiPath/uipathcli/plugin"
19
)
20

21
// The SolutionPackCommand packs a solution directory into a .uis file.
22
type SolutionPackCommand struct{}
23

24
func (c SolutionPackCommand) Command() plugin.Command {
1✔
25
        return *plugin.NewCommand("studio").
1✔
26
                WithCategory("solution", "UiPath Solution management", "Pack, unpack, push and pull UiPath Maestro solutions.").
1✔
27
                WithOperation("pack", "Pack Solution", "Packs a solution directory into a .uis file").
1✔
28
                WithParameter(plugin.NewParameter("source", plugin.ParameterTypeString, "Path to solution directory").
1✔
29
                        WithRequired(true).
1✔
30
                        WithDefaultValue(".")).
1✔
31
                WithParameter(plugin.NewParameter("destination", plugin.ParameterTypeString, "Output .uis file path").
1✔
32
                        WithDefaultValue(""))
1✔
33
}
1✔
34

35
func (c SolutionPackCommand) Execute(ctx plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
36
        source := c.getStringParameter("source", ".", ctx.Parameters)
1✔
37
        source, _ = filepath.Abs(source)
1✔
38
        destination := c.getStringParameter("destination", "", ctx.Parameters)
1✔
39

1✔
40
        fileInfo, err := os.Stat(source)
1✔
41
        if err != nil {
2✔
42
                return fmt.Errorf("Solution directory not found: %s", source)
1✔
43
        }
1✔
44
        if !fileInfo.IsDir() {
2✔
45
                return fmt.Errorf("Source is not a directory: %s", source)
1✔
46
        }
1✔
47

48
        solutionStoragePath := filepath.Join(source, "SolutionStorage.json")
1✔
49
        if _, err := os.Stat(solutionStoragePath); err != nil {
2✔
50
                return fmt.Errorf("SolutionStorage.json not found in %s. This does not appear to be a valid UiPath solution directory", source)
1✔
51
        }
1✔
52

53
        if destination == "" {
2✔
54
                destination = filepath.Base(source) + ".uis"
1✔
55
        }
1✔
56
        destination, _ = filepath.Abs(destination)
1✔
57

1✔
58
        solutionId, solutionName := c.readSolutionInfo(solutionStoragePath)
1✔
59

1✔
60
        params := newSolutionPackParams(source, destination, solutionId, solutionName)
1✔
61
        result, err := c.pack(*params)
1✔
62
        if err != nil {
2✔
63
                return err
1✔
64
        }
1✔
65

66
        jsonData, err := json.Marshal(result)
1✔
67
        if err != nil {
1✔
NEW
68
                return fmt.Errorf("Pack command failed: %w", err)
×
NEW
69
        }
×
70
        return writer.WriteResponse(*output.NewResponseInfo(http.StatusOK, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(jsonData)))
1✔
71
}
72

73
func (c SolutionPackCommand) pack(params solutionPackParams) (*solutionPackResult, error) {
1✔
74
        outFile, err := os.Create(params.Destination)
1✔
75
        if err != nil {
2✔
76
                return nil, fmt.Errorf("Cannot create output file: %w", err)
1✔
77
        }
1✔
78
        defer func() { _ = outFile.Close() }()
2✔
79

80
        zipWriter := zip.NewWriter(outFile)
1✔
81
        defer func() { _ = zipWriter.Close() }()
2✔
82

83
        err = filepath.Walk(params.Source, func(path string, info os.FileInfo, err error) error {
2✔
84
                if err != nil {
1✔
NEW
85
                        return err
×
NEW
86
                }
×
87
                relPath, err := filepath.Rel(params.Source, path)
1✔
88
                if err != nil {
1✔
NEW
89
                        return err
×
NEW
90
                }
×
91
                if c.shouldSkip(relPath, info) {
2✔
92
                        if info.IsDir() {
2✔
93
                                return filepath.SkipDir
1✔
94
                        }
1✔
95
                        return nil
1✔
96
                }
97
                if info.IsDir() {
2✔
98
                        return nil
1✔
99
                }
1✔
100
                return c.addFileToZip(zipWriter, path, relPath)
1✔
101
        })
102
        if err != nil {
2✔
103
                _ = zipWriter.Close()
1✔
104
                _ = outFile.Close()
1✔
105
                _ = os.Remove(params.Destination)
1✔
106
                return nil, err
1✔
107
        }
1✔
108

NEW
109
        // Close zip writer and file before reading size to ensure data is flushed
×
110
        _ = zipWriter.Close()
1✔
111
        _ = outFile.Close()
1✔
112

1✔
113
        fileInfo, err := os.Stat(params.Destination)
1✔
114
        size := int64(0)
1✔
115
        if err == nil {
2✔
116
                size = fileInfo.Size()
1✔
117
        }
1✔
NEW
118

×
119
        return newSucceededSolutionPackResult(params.Destination, params.SolutionId, params.SolutionName, size), nil
1✔
NEW
120
}
×
NEW
121

×
122
func (c SolutionPackCommand) shouldSkip(relPath string, info os.FileInfo) bool {
1✔
123
        if relPath == ".git" || strings.HasPrefix(relPath, ".git"+string(filepath.Separator)) {
2✔
124
                return true
1✔
125
        }
1✔
126
        if info.IsDir() && info.Name() == "__pycache__" {
2✔
127
                return true
1✔
128
        }
1✔
129
        if !info.IsDir() && strings.HasSuffix(info.Name(), ".pyc") {
2✔
130
                return true
1✔
131
        }
1✔
132
        return false
1✔
NEW
133
}
×
NEW
134

×
135
func (c SolutionPackCommand) addFileToZip(zipWriter *zip.Writer, path string, relPath string) error {
1✔
136
        zipPath := strings.ReplaceAll(relPath, string(filepath.Separator), "/")
1✔
137
        w, err := zipWriter.Create(zipPath)
1✔
138
        if err != nil {
1✔
NEW
139
                return fmt.Errorf("Error creating zip entry '%s': %w", zipPath, err)
×
NEW
140
        }
×
141
        f, err := os.Open(path)
1✔
142
        if err != nil {
2✔
143
                return fmt.Errorf("Error opening file '%s': %w", path, err)
1✔
144
        }
1✔
145
        defer func() { _ = f.Close() }()
2✔
146
        _, err = io.Copy(w, f)
1✔
147
        if err != nil {
1✔
NEW
148
                return fmt.Errorf("Error writing file '%s': %w", zipPath, err)
×
NEW
149
        }
×
150
        return nil
1✔
NEW
151
}
×
NEW
152

×
153
func (c SolutionPackCommand) readSolutionInfo(path string) (string, string) {
1✔
154
        data, err := os.ReadFile(path)
1✔
155
        if err != nil {
1✔
NEW
156
                return "", ""
×
NEW
157
        }
×
158
        var storage struct {
1✔
159
                SolutionId string `json:"SolutionId"`
1✔
160
        }
1✔
161
        err = json.Unmarshal(data, &storage)
1✔
162
        if err == nil {
2✔
163
                return storage.SolutionId, ""
1✔
164
        }
1✔
165
        return "", ""
1✔
NEW
166
}
×
NEW
167

×
168
func (c SolutionPackCommand) getStringParameter(name string, defaultValue string, parameters []plugin.ExecutionParameter) string {
1✔
169
        result := defaultValue
1✔
170
        for _, p := range parameters {
2✔
171
                if p.Name == name {
2✔
172
                        if data, ok := p.Value.(string); ok {
2✔
173
                                result = data
1✔
174
                                break
1✔
175
                        }
176
                }
NEW
177
        }
×
178
        return result
1✔
NEW
179
}
×
180

181
func NewSolutionPackCommand() *SolutionPackCommand {
1✔
182
        return &SolutionPackCommand{}
1✔
183
}
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