diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3b10bf0..2826626 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,6 +31,34 @@ jobs: path: build/packages/ if-no-files-found: error + publish_pages: + needs: build + permissions: + pages: write + id-token: write + #if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Download packages + uses: actions/download-artifact@v3 + with: + name: packages + path: build/packages/ + - name: Generate commands + run: | + tar -xzvf build/packages/uipathcli-linux-amd64.tar.gz + ./uipath commands show > documentation/commands.json + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: 'documentation' + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v2 + release: needs: build if: github.ref == 'refs/heads/main' diff --git a/commandline/command_builder.go b/commandline/command_builder.go index fcb85af..1c31722 100644 --- a/commandline/command_builder.go +++ b/commandline/command_builder.go @@ -682,6 +682,23 @@ func (b CommandBuilder) createConfigSetCommand() *cli.Command { } func (b CommandBuilder) loadDefinitions(args []string, version string) ([]parser.Definition, error) { + if len(args) > 1 && args[1] == "commands" { + all, err := b.DefinitionProvider.Index(version) + if err != nil { + return nil, err + } + definitions := []parser.Definition{} + for _, d := range all { + definition, err := b.DefinitionProvider.Load(d.Name, version) + if err != nil { + return nil, err + } + if definition != nil { + definitions = append(definitions, *definition) + } + } + return definitions, nil + } if len(args) <= 1 || strings.HasPrefix(args[1], "--") { return b.DefinitionProvider.Index(version) } @@ -699,6 +716,43 @@ func (b CommandBuilder) loadAutocompleteDefinitions(args []string, version strin return b.loadDefinitions(args, version) } +func (b CommandBuilder) createShowCommand(definitions []parser.Definition, commands []*cli.Command) *cli.Command { + return &cli.Command{ + Name: "commands", + Description: "Command to inspect available uipath CLI operations", + Flags: []cli.Flag{ + b.HelpFlag(), + }, + Subcommands: []*cli.Command{ + { + Name: "show", + Description: "Print available uipath CLI commands", + Flags: []cli.Flag{ + b.HelpFlag(), + }, + Action: func(context *cli.Context) error { + flagBuilder := newFlagBuilder() + flagBuilder.AddFlags(b.CreateDefaultFlags(false)) + flagBuilder.AddFlag(b.HelpFlag()) + flags := flagBuilder.ToList() + + handler := newShowCommandHandler() + output, err := handler.Execute(definitions, flags) + if err != nil { + return err + } + fmt.Fprintln(b.StdOut, output) + return nil + }, + HideHelp: true, + Hidden: true, + }, + }, + HideHelp: true, + Hidden: true, + } +} + func (b CommandBuilder) createServiceCommands(definitions []parser.Definition) []*cli.Command { commands := []*cli.Command{} for _, e := range definitions { @@ -740,7 +794,8 @@ func (b CommandBuilder) Create(args []string) ([]*cli.Command, error) { servicesCommands := b.createServiceCommands(definitions) autocompleteCommand := b.createAutoCompleteCommand(version) configCommand := b.createConfigCommand() - commands := append(servicesCommands, autocompleteCommand, configCommand) + showCommand := b.createShowCommand(definitions, servicesCommands) + commands := append(servicesCommands, autocompleteCommand, configCommand, showCommand) return commands, nil } diff --git a/commandline/definition_file_store.go b/commandline/definition_file_store.go index e12425a..9235145 100644 --- a/commandline/definition_file_store.go +++ b/commandline/definition_file_store.go @@ -56,7 +56,6 @@ func (s *DefinitionFileStore) Read(name string, version string) (*DefinitionData return nil, err } definition := NewDefinitionData(name, version, data) - s.definitions = append(s.definitions, *definition) return definition, err } } diff --git a/commandline/parameter_formatter.go b/commandline/parameter_formatter.go index 75866c3..a6b3b10 100644 --- a/commandline/parameter_formatter.go +++ b/commandline/parameter_formatter.go @@ -15,6 +15,10 @@ func (f parameterFormatter) Description() string { return f.description(f.parameter) } +func (f parameterFormatter) UsageExample() string { + return f.usageExample(f.parameter) +} + func (f parameterFormatter) description(parameter parser.Parameter) string { builder := strings.Builder{} diff --git a/commandline/show_command_handler.go b/commandline/show_command_handler.go new file mode 100644 index 0000000..625bab6 --- /dev/null +++ b/commandline/show_command_handler.go @@ -0,0 +1,177 @@ +package commandline + +import ( + "encoding/json" + "sort" + + "github.com/UiPath/uipathcli/parser" + "github.com/urfave/cli/v2" +) + +// showCommandHandler prints all available uipathcli commands +type showCommandHandler struct { +} + +type parameterJson struct { + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` + Required bool `json:"required"` + AllowedValues []interface{} `json:"allowedValues"` + DefaultValue interface{} `json:"defaultValue"` + Example string `json:"example"` +} + +type commandJson struct { + Name string `json:"name"` + Description string `json:"description"` + Parameters []parameterJson `json:"parameters"` + Subcommands []commandJson `json:"subcommands"` +} + +func (h showCommandHandler) Execute(definitions []parser.Definition, globalFlags []cli.Flag) (string, error) { + result := commandJson{ + Name: "uipath", + Description: "Command line interface to simplify, script and automate API calls for UiPath services", + Parameters: h.convertFlagsToCommandParameters(globalFlags), + Subcommands: h.convertDefinitionsToCommands(definitions), + } + bytes, err := json.MarshalIndent(result, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} + +func (h showCommandHandler) convertDefinitionsToCommands(definitions []parser.Definition) []commandJson { + commands := []commandJson{} + for _, d := range definitions { + command := h.convertDefinitionToCommands(d) + commands = append(commands, command) + } + return commands +} + +func (h showCommandHandler) convertDefinitionToCommands(definition parser.Definition) commandJson { + categories := map[string]commandJson{} + + for _, op := range definition.Operations { + if op.Category == nil { + command := h.convertOperationToCommand(op) + categories[command.Name] = command + } else { + h.createOrUpdateCategory(op, categories) + } + } + + commands := []commandJson{} + for _, command := range categories { + commands = append(commands, command) + } + + h.sort(commands) + for _, command := range commands { + h.sort(command.Subcommands) + } + return commandJson{ + Name: definition.Name, + Subcommands: commands, + } +} + +func (h showCommandHandler) createOrUpdateCategory(operation parser.Operation, categories map[string]commandJson) { + command, found := categories[operation.Category.Name] + if !found { + command = h.createCategoryCommand(operation) + } + command.Subcommands = append(command.Subcommands, h.convertOperationToCommand(operation)) + categories[operation.Category.Name] = command +} + +func (h showCommandHandler) createCategoryCommand(operation parser.Operation) commandJson { + return commandJson{ + Name: operation.Category.Name, + Description: operation.Category.Description, + } +} + +func (h showCommandHandler) convertOperationToCommand(operation parser.Operation) commandJson { + return commandJson{ + Name: operation.Name, + Description: operation.Description, + Parameters: h.convertParametersToCommandParameters(operation.Parameters), + } +} + +func (h showCommandHandler) convertFlagsToCommandParameters(flags []cli.Flag) []parameterJson { + result := []parameterJson{} + for _, f := range flags { + result = append(result, h.convertFlagToCommandParameter(f)) + } + return result +} + +func (h showCommandHandler) convertParametersToCommandParameters(parameters []parser.Parameter) []parameterJson { + result := []parameterJson{} + for _, p := range parameters { + result = append(result, h.convertParameterToCommandParameter(p)) + } + return result +} + +func (h showCommandHandler) convertFlagToCommandParameter(flag cli.Flag) parameterJson { + intFlag, ok := flag.(*cli.IntFlag) + if ok { + return parameterJson{ + Name: intFlag.Name, + Description: intFlag.Usage, + Type: "integer", + Required: false, + AllowedValues: []interface{}{}, + DefaultValue: intFlag.Value, + } + } + boolFlag, ok := flag.(*cli.BoolFlag) + if ok { + return parameterJson{ + Name: boolFlag.Name, + Description: boolFlag.Usage, + Type: "boolean", + Required: false, + AllowedValues: []interface{}{}, + DefaultValue: boolFlag.Value, + } + } + stringFlag := flag.(*cli.StringFlag) + return parameterJson{ + Name: stringFlag.Name, + Description: stringFlag.Usage, + Type: "string", + Required: false, + AllowedValues: []interface{}{}, + DefaultValue: stringFlag.Value, + } +} + +func (h showCommandHandler) convertParameterToCommandParameter(parameter parser.Parameter) parameterJson { + formatter := newParameterFormatter(parameter) + return parameterJson{ + Name: parameter.Name, + Description: parameter.Description, + Type: parameter.Type, + Required: parameter.Required, + AllowedValues: parameter.AllowedValues, + DefaultValue: parameter.DefaultValue, + Example: formatter.UsageExample(), + } +} + +func (h showCommandHandler) sort(commands []commandJson) { + sort.Slice(commands, func(i, j int) bool { + return commands[i].Name < commands[j].Name + }) +} + +func newShowCommandHandler() *showCommandHandler { + return &showCommandHandler{} +} diff --git a/documentation/css/main.css b/documentation/css/main.css new file mode 100644 index 0000000..cc24a34 --- /dev/null +++ b/documentation/css/main.css @@ -0,0 +1,166 @@ +* { + color: #273139; + font-family: noto-sans, "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", "Noto Sans", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,sans-serif; +} + +body { + margin: 0; + padding: 0; + position: relative; + min-height: 100vh; +} + +h1 { + font-size: 26px; +} + +h2 { + font-size: 20px; + border-bottom: 1px solid rgb(164, 177, 184); + width: 90%; +} + +a { + color: #0067df; + text-decoration: none; + font-weight: 600; +} + +.content { + padding-bottom: 50px; +} + +.main { + margin: 20px; +} + +.header { + height: 48px; + border-bottom: 1px solid rgb(207, 216, 221); + display: flex; + flex-direction: row; +} + +.header-icon { + padding-top: 6px; + padding-left: 8px; + line-height: 48px; + vertical-align: middle; +} + +.header-text { + padding-left: 15px; + font-size: 16px; + font-weight: 600; + line-height: 48px; + vertical-align: middle; +} + +.breadcrumb +{ + color: #526069; + font-size: 14px; + font-weight: 600; + display: block; +} + +.breadcrumb:after { + content: ''; + display: block; + clear: both; +} + +.breadcrumb ol { + padding-left: 0; + list-style: none; +} + +.breadcrumb li { + float: left; +} + +.breadcrumb li:after +{ + content: '/'; + padding-left: 8px; + padding-right: 8px; + display: inline; +} + +.breadcrumb li:last-child +{ + font-weight: 400; +} + +.footer { + position: absolute; + bottom: 0; + margin-top: 20px; + height: 50px; + width: 100%; + background: rgb(29, 29, 30); + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.footer-icon { + padding-left: 8px; + margin-top: 10px; +} + +.footer-text { + padding-right: 8px; + margin-top: 15px; + color: rgb(89, 90, 92); +} + +.usage { + padding: 5px; + background-color: #f7f7f7; + border-radius: 4px; + border: 1px solid #e1e1e8; + font-family: monospace; +} + +.parameters { + padding-left: 0; + list-style: none; +} + +.parameter { + margin: 20px; +} + +.parameter-name { + padding: 2px; + background-color: #f7f7f7; + border-radius: 4px; + border: 1px solid #e1e1e8; + font-family: monospace; +} + +.parameter-description { + margin-top: 10px; + margin-bottom: 10px; + width: 100%; +} + +.parameter-allowed-values ul { + list-style-type: square; +} + +.parameter-example-code { + font-family: monospace; + font-size: 14px; + margin: 5px 0 0 20px; +} + +code { + font-family: monospace; + font-size: 14px; + padding: 2px; + background-color: #f7f7f7; + border-radius: 4px; + border: 1px solid #e1e1e8; +} \ No newline at end of file diff --git a/documentation/favicon.ico b/documentation/favicon.ico new file mode 100644 index 0000000..13c1d6d Binary files /dev/null and b/documentation/favicon.ico differ diff --git a/documentation/index.html b/documentation/index.html new file mode 100644 index 0000000..1ff07dd --- /dev/null +++ b/documentation/index.html @@ -0,0 +1,36 @@ + + +
+ ++ In order to use client credentials, you need to set up an External Application (Confidential) and generate an application secret. +
+Run the interactive CLI configuration:
+uipath config --auth credentials
+ + The CLI will ask you to enter the main config settings like +
After that the CLI should be ready and you can validate that it is working by invoking one of the services (requires OR.Users.Read scope):
+uipath orchestrator users get
+ + In order to use oauth login, you need to set up an External Application (Non-Confidential) with a redirect url which points to your local CLI: +
+Run the interactive CLI configuration:
+uipath config --auth login
+ + The CLI will ask you to enter the main config settings like +
After that the CLI should be ready and you can validate that it is working by invoking one of the services:
+uipath orchestrator users get
+ ${parameter.description}
+"${parameter.example}"
` : ''} +${parameter.description}
+"${parameter.example}"
` : ''} +