From 518adaf63d8d7c1ba5d9af234e18bfadd3a26710 Mon Sep 17 00:00:00 2001 From: Thomas Schmitt Date: Mon, 2 Oct 2023 10:46:43 +0300 Subject: [PATCH] Generate commands json and render uipathcli documentation pages - Added a new command to show all available commands - Implemented simple website to display the available commands - Extended github actions CI workflow to deploy static assets on GitHub Pages --- .github/workflows/ci.yaml | 28 +++++ commandline/command_builder.go | 57 ++++++++- commandline/definition_file_store.go | 1 - commandline/parameter_formatter.go | 4 + commandline/show_command_handler.go | 177 +++++++++++++++++++++++++++ documentation/css/main.css | 174 ++++++++++++++++++++++++++ documentation/favicon.ico | Bin 0 -> 3525 bytes documentation/index.html | 38 ++++++ documentation/js/main.mjs | 53 ++++++++ documentation/js/template.mjs | 165 +++++++++++++++++++++++++ test/show_command_test.go | 104 ++++++++++++++++ 11 files changed, 799 insertions(+), 2 deletions(-) create mode 100644 commandline/show_command_handler.go create mode 100644 documentation/css/main.css create mode 100644 documentation/favicon.ico create mode 100644 documentation/index.html create mode 100644 documentation/js/main.mjs create mode 100644 documentation/js/template.mjs create mode 100644 test/show_command_test.go 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..7d4e7f4 --- /dev/null +++ b/documentation/css/main.css @@ -0,0 +1,174 @@ +* { + 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; + font-size: 16px; +} + +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; +} + +code { + font-family: monospace; + font-size: 14px; + padding: 2px; + background-color: #f7f7f7; + border-radius: 4px; + border: 1px solid #e1e1e8; +} + +.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-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: 15px; + margin-top: 14px; + color: rgb(89, 90, 92); +} + +.services li { + padding-bottom: 5px; +} + +.commands li { + padding-bottom: 5px; +} + +.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; +} \ No newline at end of file diff --git a/documentation/favicon.ico b/documentation/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..13c1d6d102e6e0d0c46a1dafe0e2d42226ac1997 GIT binary patch literal 3525 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 literal 0 HcmV?d00001 diff --git a/documentation/index.html b/documentation/index.html new file mode 100644 index 0000000..a36f234 --- /dev/null +++ b/documentation/index.html @@ -0,0 +1,38 @@ + + + + + + uipathcli - Documentation + + + + +
+
+ + + UiPath Logo + + + + uipathcli - Documentation +
+
+
+ + + + \ No newline at end of file diff --git a/documentation/js/main.mjs b/documentation/js/main.mjs new file mode 100644 index 0000000..84b5f38 --- /dev/null +++ b/documentation/js/main.mjs @@ -0,0 +1,53 @@ +import { Template } from "./template.mjs"; + +function findCommand(command, name) { + return command.subcommands.find(c => c.name === name); +} + +function render(command, url) { + let serviceName = null; + let serviceCommand = null; + let category = null; + let categoryCommand = null; + let operation = null; + let operationCommand = null; + + const args = url.split('/'); + if (args.length >= 2) { + serviceName = args[1]; + serviceCommand = findCommand(command, serviceName); + } + if (args.length >= 3 && serviceCommand != null) { + category = args[2]; + categoryCommand = findCommand(serviceCommand, category); + } + if (args.length >= 4 && categoryCommand != null) { + operation = args[3]; + operationCommand = findCommand(categoryCommand, operation); + } + + const template = new Template(); + if (operationCommand != null) { + return template.operation(command.name, serviceName, category, operationCommand); + } + if (categoryCommand != null) { + return template.category(command.name, serviceName, categoryCommand); + } + if (serviceCommand != null) { + return template.service(command.name, serviceCommand); + } + return template.main(command); +} + +export async function main() { + const response = await fetch("commands.json"); + const command = await response.json(); + const element = document.querySelector('.main'); + + window.onhashchange = function() { + const template = render(command, window.location.hash); + element.innerHTML = template; + }; + const template = render(command, window.location.hash); + element.innerHTML = template; +} \ No newline at end of file diff --git a/documentation/js/template.mjs b/documentation/js/template.mjs new file mode 100644 index 0000000..156ac7f --- /dev/null +++ b/documentation/js/template.mjs @@ -0,0 +1,165 @@ +export class Template { + main(command) { + return ` +

${command.name}

+

Description

+
${command.description}
+

Available Services

+ +

Configuration

+
+ The CLI supports multiple ways to authorize with the UiPath services: +
    +
  • Client Credentials: Generate secret and configure the CLI to use these long-term credentials. Client credentials should be used in case you want to use the CLI from a script in an automated way.
  • +
  • OAuth Login: Login to UiPath using your browser and SSO of choice. This is the preferred flow when you are using the CLI interactively. No need to manage any credentials.
  • +
+
+

Client Credentials

+
+

+ 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 +

    +
  • organization and tenant used by UiPath services which are account-scoped or tenant-scoped
  • +
  • clientId and clientSecret to retrieve the JWT bearer token for authentication
  • +
+

+

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 +
+

OAuth Login

+
+

+ 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 +

    +
  • organization and tenant used by UiPath services which are account-scoped or tenant-scoped
  • +
  • clientId, redirectUri and scopes which are needed to initiate the OAuth flow
  • +
+

+

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 +
+

Global Parameters

+
    + ${command.parameters.map(parameter => ` +
  • + --${parameter.name} ${parameter.type} + ${parameter.defaultValue ? `(default: ${parameter.defaultValue})` : ''} +

    ${parameter.description}

    +
    ${parameter.allowedValues && parameter.allowedValues.length > 0 ? + `Allowed Values: +
      + ${parameter.allowedValues.map(value => ` +
    • ${value}
    • + `).join('')} +
    ` : ''} +
    +
    ${parameter.example ? ` + Example:
    +

    "${parameter.example}"

    ` : ''} +
    +
  • + `).join('')} +
+ `; + } + + service(executableName, command) { + return ` + +

${executableName} ${command.name}

+

Description

+
${command.description}
+

Available Commands

+ + `; + } + + category(executableName, serviceName, command) { + return ` + +

${executableName} ${serviceName} ${command.name}

+

Description

+
${command.description}
+

Available Commands

+ + `; + } + + operation(executableName, serviceName, categoryName, command) { + return ` + +

${executableName} ${serviceName} ${categoryName} ${command.name}

+

Description

+
${command.description}
+

Usage

+
+ ${executableName} ${serviceName} ${categoryName} ${command.name}
+ ${command.parameters.map(parameter => ` +   --${parameter.name} ${parameter.type}
+ `).join('')} +
+

Parameters

+
    + ${command.parameters.map(parameter => ` +
  • + --${parameter.name} ${parameter.type} + ${parameter.defaultValue ? `(default: ${parameter.defaultValue})` : ''} +

    ${parameter.description}

    +
    ${parameter.allowedValues && parameter.allowedValues.length > 0 ? + `Allowed Values: +
      + ${parameter.allowedValues.map(value => ` +
    • ${value}
    • + `).join('')} +
    ` : ''} +
    +
    ${parameter.example ? ` + Example:
    +

    "${parameter.example}"

    ` : ''} +
    +
  • + `).join('')} +
+ `; + } +} \ No newline at end of file diff --git a/test/show_command_test.go b/test/show_command_test.go new file mode 100644 index 0000000..a3f2103 --- /dev/null +++ b/test/show_command_test.go @@ -0,0 +1,104 @@ +package test + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestCommandReturnedSuccessfully(t *testing.T) { + definition := ` +paths: + /ping: + get: + summary: Simple ping + operationId: ping + tags: + - health +` + context := NewContextBuilder(). + WithDefinition("myservice", definition). + Build() + + result := RunCli([]string{"commands", "show"}, context) + + command := GetCommand(t, result) + name := command["name"] + if name != "uipath" { + t.Errorf("Unexpected executable name in output, got: %v", name) + } + + serviceCommand := GetSubcommands(command)[0] + serviceName := serviceCommand["name"] + if serviceName != "myservice" { + t.Errorf("Unexpected service name in output, got: %v", serviceName) + } + + categoryCommand := GetSubcommands(serviceCommand)[0] + categoryName := categoryCommand["name"] + if categoryName != "health" { + t.Errorf("Unexpected category name in output, got: %v", categoryName) + } + + operationCommand := GetSubcommands(categoryCommand)[0] + operationName := operationCommand["name"] + if operationName != "ping" { + t.Errorf("Unexpected operation name in output, got: %v", operationName) + } +} + +func TestCommandGlobalFlags(t *testing.T) { + definition := ` +paths: + /ping: + get: + summary: Simple ping + operationId: ping + tags: + - health +` + context := NewContextBuilder(). + WithDefinition("myservice", definition). + Build() + + result := RunCli([]string{"commands", "show"}, context) + + command := GetCommand(t, result) + parameters := GetParameters(command) + + names := []string{} + for _, parameter := range parameters { + names = append(names, parameter["name"].(string)) + } + + expectedNames := []string{"debug", "profile", "uri", "organization", "tenant", "insecure", "output", "query", "wait", "wait-timeout", "file", "version", "help"} + if !reflect.DeepEqual(names, expectedNames) { + t.Errorf("Unexpected global parameters in output, expected: %v but got: %v", expectedNames, names) + } +} + +func GetCommand(t *testing.T, result Result) map[string]interface{} { + command := map[string]interface{}{} + err := json.Unmarshal([]byte(result.StdOut), &command) + if err != nil { + t.Errorf("Failed to deserialize show commands result %v", err) + } + return command +} + +func GetSubcommands(command map[string]interface{}) []map[string]interface{} { + return GetArray(command, "subcommands") +} + +func GetParameters(command map[string]interface{}) []map[string]interface{} { + return GetArray(command, "parameters") +} + +func GetArray(section map[string]interface{}, name string) []map[string]interface{} { + array := section[name].([]interface{}) + result := []map[string]interface{}{} + for _, item := range array { + result = append(result, item.(map[string]interface{})) + } + return result +}