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

UiPath / uipathcli / 13651495225

04 Mar 2025 10:22AM UTC coverage: 89.903% (-0.3%) from 90.225%
13651495225

push

github

thschmitt
Add command to publish studio packages

Implemented new command `uipath studio package publish` which simplifies
uploading packages to orchestrator.

Clients can now just pack and publish with two simple commands:

```
uipath studio package pack
uipath studio package publish
```

230 of 273 new or added lines in 7 files covered. (84.25%)

1 existing line in 1 file now uncovered.

5360 of 5962 relevant lines covered (89.9%)

1.01 hits per line

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

81.58
/plugin/studio/package_publish_command.go
1
package studio
2

3
import (
4
        "bytes"
5
        "crypto/tls"
6
        "encoding/json"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "mime/multipart"
11
        "net/http"
12
        "net/textproto"
13
        "net/url"
14
        "os"
15
        "path/filepath"
16
        "strings"
17

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

25
// The PackagePublishCommand publishes a package
26
type PackagePublishCommand struct {
27
}
28

29
func (c PackagePublishCommand) Command() plugin.Command {
1✔
30
        return *plugin.NewCommand("studio").
1✔
31
                WithCategory("package", "Package", "UiPath Studio package-related actions").
1✔
32
                WithOperation("publish", "Publish Package", "Publishes the package to orchestrator").
1✔
33
                WithParameter("source", plugin.ParameterTypeString, "Path to package (default: .)", false)
1✔
34
}
1✔
35

36
func (c PackagePublishCommand) Execute(context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
37
        if context.Organization == "" {
2✔
38
                return errors.New("Organization is not set")
1✔
39
        }
1✔
40
        if context.Tenant == "" {
2✔
41
                return errors.New("Tenant is not set")
1✔
42
        }
1✔
43
        source, err := c.getSource(context)
1✔
44
        if err != nil {
2✔
45
                return err
1✔
46
        }
1✔
47
        nupkgReader := newNupkgReader(source)
1✔
48
        nuspec, err := nupkgReader.ReadNuspec()
1✔
49
        if err != nil {
2✔
50
                return err
1✔
51
        }
1✔
52
        baseUri := c.formatUri(context.BaseUri, context.Organization, context.Tenant)
1✔
53
        params := newPackagePublishParams(source, nuspec.Title, nuspec.Version, baseUri, context.Auth, context.Insecure, context.Debug)
1✔
54
        result, err := c.publish(*params, logger)
1✔
55
        if err != nil {
2✔
56
                return err
1✔
57
        }
1✔
58

59
        json, err := json.Marshal(result)
1✔
60
        if err != nil {
1✔
NEW
61
                return fmt.Errorf("Publish command failed: %v", err)
×
NEW
62
        }
×
63
        return writer.WriteResponse(*output.NewResponseInfo(200, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(json)))
1✔
64
}
65

66
func (c PackagePublishCommand) publish(params packagePublishParams, logger log.Logger) (*packagePublishResult, error) {
1✔
67
        file := stream.NewFileStream(params.Source)
1✔
68
        uploadBar := visualization.NewProgressBar(logger)
1✔
69
        defer uploadBar.Remove()
1✔
70
        requestError := make(chan error)
1✔
71
        request, err := c.createUploadRequest(file, params, uploadBar, requestError)
1✔
72
        if err != nil {
1✔
NEW
73
                return nil, err
×
NEW
74
        }
×
75
        if params.Debug {
2✔
76
                c.logRequest(logger, request)
1✔
77
        }
1✔
78
        response, err := c.send(request, params.Insecure, requestError)
1✔
79
        if err != nil {
1✔
NEW
80
                return nil, fmt.Errorf("Error sending request: %w", err)
×
NEW
81
        }
×
82
        defer response.Body.Close()
1✔
83
        body, err := io.ReadAll(response.Body)
1✔
84
        if err != nil {
1✔
NEW
85
                return nil, fmt.Errorf("Error reading response: %w", err)
×
NEW
86
        }
×
87
        c.logResponse(logger, response, body)
1✔
88
        if response.StatusCode == http.StatusConflict {
2✔
89
                errorMessage := fmt.Sprintf("Package '%s' already exists", filepath.Base(params.Source))
1✔
90
                return newFailedPackagePublishResult(errorMessage, params.Source, params.Name, params.Version), nil
1✔
91
        }
1✔
92
        if response.StatusCode != http.StatusOK {
2✔
93
                return nil, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
94
        }
1✔
95
        return newSucceededPackagePublishResult(params.Source, params.Name, params.Version), nil
1✔
96
}
97

98
func (c PackagePublishCommand) createUploadRequest(file stream.Stream, params packagePublishParams, uploadBar *visualization.ProgressBar, requestError chan error) (*http.Request, error) {
1✔
99
        bodyReader, bodyWriter := io.Pipe()
1✔
100
        contentType, contentLength := c.writeMultipartBody(bodyWriter, file, "application/octet-stream", requestError)
1✔
101
        uploadReader := c.progressReader("uploading...", "completing  ", bodyReader, contentLength, uploadBar)
1✔
102

1✔
103
        uri := params.BaseUri + "/odata/Processes/UiPath.Server.Configuration.OData.UploadPackage"
1✔
104
        request, err := http.NewRequest("POST", uri, uploadReader)
1✔
105
        if err != nil {
1✔
NEW
106
                return nil, err
×
NEW
107
        }
×
108
        request.Header.Add("Content-Type", contentType)
1✔
109
        for key, value := range params.Auth.Header {
1✔
NEW
110
                request.Header.Add(key, value)
×
NEW
111
        }
×
112
        return request, nil
1✔
113
}
114

115
func (c PackagePublishCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader {
1✔
116
        if length < 10*1024*1024 {
2✔
117
                return reader
1✔
118
        }
1✔
NEW
119
        progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) {
×
NEW
120
                displayText := text
×
NEW
121
                if progress.Completed {
×
NEW
122
                        displayText = completedText
×
NEW
123
                }
×
NEW
124
                progressBar.UpdateProgress(displayText, progress.BytesRead, length, progress.BytesPerSecond)
×
125
        })
NEW
126
        return progressReader
×
127
}
128

129
func (c PackagePublishCommand) calculateMultipartSize(stream stream.Stream) int64 {
1✔
130
        size, _ := stream.Size()
1✔
131
        return size
1✔
132
}
1✔
133

134
func (c PackagePublishCommand) writeMultipartForm(writer *multipart.Writer, stream stream.Stream, contentType string) error {
1✔
135
        filePart := textproto.MIMEHeader{}
1✔
136
        filePart.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, stream.Name()))
1✔
137
        filePart.Set("Content-Type", contentType)
1✔
138
        w, err := writer.CreatePart(filePart)
1✔
139
        if err != nil {
1✔
NEW
140
                return fmt.Errorf("Error creating form field 'file': %w", err)
×
NEW
141
        }
×
142
        data, err := stream.Data()
1✔
143
        if err != nil {
1✔
NEW
144
                return err
×
NEW
145
        }
×
146
        defer data.Close()
1✔
147
        _, err = io.Copy(w, data)
1✔
148
        if err != nil {
1✔
NEW
149
                return fmt.Errorf("Error writing form field 'file': %w", err)
×
NEW
150
        }
×
151
        return nil
1✔
152
}
153

154
func (c PackagePublishCommand) writeMultipartBody(bodyWriter *io.PipeWriter, stream stream.Stream, contentType string, errorChan chan error) (string, int64) {
1✔
155
        contentLength := c.calculateMultipartSize(stream)
1✔
156
        formWriter := multipart.NewWriter(bodyWriter)
1✔
157
        go func() {
2✔
158
                defer bodyWriter.Close()
1✔
159
                defer formWriter.Close()
1✔
160
                err := c.writeMultipartForm(formWriter, stream, contentType)
1✔
161
                if err != nil {
1✔
NEW
162
                        errorChan <- err
×
NEW
163
                        return
×
NEW
164
                }
×
165
        }()
166
        return formWriter.FormDataContentType(), contentLength
1✔
167
}
168

169
func (c PackagePublishCommand) send(request *http.Request, insecure bool, errorChan chan error) (*http.Response, error) {
1✔
170
        responseChan := make(chan *http.Response)
1✔
171
        go func(request *http.Request) {
2✔
172
                response, err := c.sendRequest(request, insecure)
1✔
173
                if err != nil {
1✔
NEW
174
                        errorChan <- err
×
NEW
175
                        return
×
NEW
176
                }
×
177
                responseChan <- response
1✔
178
        }(request)
179

180
        select {
1✔
NEW
181
        case err := <-errorChan:
×
NEW
182
                return nil, err
×
183
        case response := <-responseChan:
1✔
184
                return response, nil
1✔
185
        }
186
}
187

188
func (c PackagePublishCommand) sendRequest(request *http.Request, insecure bool) (*http.Response, error) {
1✔
189
        transport := &http.Transport{
1✔
190
                TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint // This is user configurable and disabled by default
1✔
191
        }
1✔
192
        client := &http.Client{Transport: transport}
1✔
193
        return client.Do(request)
1✔
194
}
1✔
195

196
func (c PackagePublishCommand) getSource(context plugin.ExecutionContext) (string, error) {
1✔
197
        source := c.getParameter("source", ".", context.Parameters)
1✔
198
        source, _ = filepath.Abs(source)
1✔
199
        fileInfo, err := os.Stat(source)
1✔
200
        if err != nil {
2✔
201
                return "", fmt.Errorf("Package not found.")
1✔
202
        }
1✔
203
        if fileInfo.IsDir() {
2✔
204
                source = findLatestNupkg(source)
1✔
205
        }
1✔
206
        if source == "" {
1✔
NEW
207
                return "", errors.New("Could not find package to publish")
×
NEW
208
        }
×
209
        return source, nil
1✔
210
}
211

212
func (c PackagePublishCommand) getParameter(name string, defaultValue string, parameters []plugin.ExecutionParameter) string {
1✔
213
        result := defaultValue
1✔
214
        for _, p := range parameters {
2✔
215
                if p.Name == name {
2✔
216
                        if data, ok := p.Value.(string); ok {
2✔
217
                                result = data
1✔
218
                                break
1✔
219
                        }
220
                }
221
        }
222
        return result
1✔
223
}
224

225
func (c PackagePublishCommand) formatUri(baseUri url.URL, org string, tenant string) string {
1✔
226
        path := baseUri.Path
1✔
227
        if baseUri.Path == "" {
2✔
228
                path = "/{organization}/{tenant}/orchestrator_"
1✔
229
        }
1✔
230
        path = strings.ReplaceAll(path, "{organization}", org)
1✔
231
        path = strings.ReplaceAll(path, "{tenant}", tenant)
1✔
232
        path = strings.TrimSuffix(path, "/")
1✔
233
        return fmt.Sprintf("%s://%s%s", baseUri.Scheme, baseUri.Host, path)
1✔
234
}
235

236
func (c PackagePublishCommand) logRequest(logger log.Logger, request *http.Request) {
1✔
237
        buffer := &bytes.Buffer{}
1✔
238
        _, _ = buffer.ReadFrom(request.Body)
1✔
239
        body := buffer.Bytes()
1✔
240
        request.Body = io.NopCloser(bytes.NewReader(body))
1✔
241
        requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, bytes.NewReader(body))
1✔
242
        logger.LogRequest(*requestInfo)
1✔
243
}
1✔
244

245
func (c PackagePublishCommand) logResponse(logger log.Logger, response *http.Response, body []byte) {
1✔
246
        responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
1✔
247
        logger.LogResponse(*responseInfo)
1✔
248
}
1✔
249

250
func NewPackagePublishCommand() *PackagePublishCommand {
1✔
251
        return &PackagePublishCommand{}
1✔
252
}
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