From 5b5325dd8090a6bf4cfc5133adf358eee9668931 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:04:46 +0100 Subject: [PATCH 01/25] feat: get all authorized orgs from command 'sf org auth', and display the list --- src/autocomplete/zsh.ts | 19 +++++++++++++++++-- src/commands/autocomplete/create.ts | 8 ++++++++ src/commands/autocomplete/index.ts | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index afa5de8c..0ee98334 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -84,6 +84,13 @@ export default class ZshCompWithSpaces { return `#compdef ${this.config.bin} ${this.config.binAliases?.map((a) => `compdef ${a}=${this.config.bin}`).join('\n') ?? ''} +_orgs(){ + local completions + completions=($(sf autocomplete --orgs)) + + _describe -t completions 'completions' completions && return 0 +} + ${this.topics.map((t) => this.genZshTopicCompFun(t.name)).join('\n')} _${this.config.bin}() { @@ -159,7 +166,11 @@ _${this.config.bin} flagSpec += `"[${flagSummary}]` - flagSpec += f.options ? `:${f.name} options:(${f.options?.join(' ')})"` : ':file:_files"' + flagSpec += f.options + ? `:${f.name} options:(${f.options?.join(' ')})"` + : f.name === 'target-org' + ? ':org:_orgs"' + : ':file:_files"' } else { if (f.multiple) { // this flag can be present multiple times on the line @@ -168,7 +179,11 @@ _${this.config.bin} flagSpec += `--${f.name}"[${flagSummary}]:` - flagSpec += f.options ? `${f.name} options:(${f.options.join(' ')})"` : 'file:_files"' + flagSpec += f.options + ? `${f.name} options:(${f.options.join(' ')})"` + : f.name === 'target-org' + ? ':org:_orgs"' + : ':file:_files"' } } else if (f.char) { // Flag.Boolean diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index 3510e3f1..cd3d7776 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -224,6 +224,14 @@ export default class Create extends AutocompleteBase { return `#compdef ${cliBin} +_orgs(){ + local completions + completions=($(sf autocomplete --orgs)) + + _describe -t completions 'completions' completions && return 0 +} + + _${cliBin} () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index fa73074d..1b73ee03 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -1,5 +1,6 @@ import {Args, Flags, ux} from '@oclif/core' import chalk from 'chalk' +import {execSync} from 'node:child_process' import {EOL} from 'node:os' import {AutocompleteBase} from '../../base.js' @@ -49,6 +50,7 @@ export default class Index extends AutocompleteBase { ] static flags = { + orgs: Flags.boolean({char: 'o', description: 'Get authenticated orgs'}), 'refresh-cache': Flags.boolean({char: 'r', description: 'Refresh cache (ignores displaying instructions)'}), } @@ -62,6 +64,18 @@ export default class Index extends AutocompleteBase { ) } + // Build the current list of authenticated orgs + if (flags.orgs) { + const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + let result: string = '' + for (const element of orgsJson.result) { + result += (element.alias ? element.alias + ':' + element.username : element.username + ':') + '\n' + } + + this.log(result) + this.exit() + } + ux.action.start(`${chalk.bold('Building the autocomplete cache')}`) await Create.run([], this.config) ux.action.stop() From 7e179fa1eb3e13341b66de7bea4caecded2f5072 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:46:46 +0100 Subject: [PATCH 02/25] feat: works for bash --- src/autocomplete/bash-spaces.ts | 67 +++++++++++++++++++++-------- src/autocomplete/bash.ts | 38 +++++++++++++++- src/commands/autocomplete/create.ts | 17 ++++++++ 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/autocomplete/bash-spaces.ts b/src/autocomplete/bash-spaces.ts index 2bd3ad98..f6ddba30 100644 --- a/src/autocomplete/bash-spaces.ts +++ b/src/autocomplete/bash-spaces.ts @@ -14,6 +14,28 @@ __autocomplete() " + local orgs=" + +" + local targetOrgFlags=("--target-org" "-o") + + function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found + } + + function _suggestOrgs(){ + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi + } + function __trim_colon_commands() { # Turn $commands into an array @@ -38,26 +60,31 @@ __autocomplete() } if [[ "$cur" != "-"* ]]; then - # Command - __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else - # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") - normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" + # Command + __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) - # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") - colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") + normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" - if [[ -z "$normalizedCommand" ]]; then - # If there is no normalizedCommand yet the user hasn't typed in a full command - # So we should trim all subcommands & flags from $commands so we can suggest all top level commands - opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') - else - # Filter $commands to just the ones that match the $normalizedCommand and turn into an array - commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) - # Trim higher level and subcommands from the subcommands to suggest - __trim_colon_commands "$colonPrefix" + # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") + colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + + if [[ -z "$normalizedCommand" ]]; then + # If there is no normalizedCommand yet the user hasn't typed in a full command + # So we should trim all subcommands & flags from $commands so we can suggest all top level commands + opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') + else + # Filter $commands to just the ones that match the $normalizedCommand and turn into an array + commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) + # Trim higher level and subcommands from the subcommands to suggest + __trim_colon_commands "$colonPrefix" - opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + fi fi else # Flag @@ -69,9 +96,15 @@ __autocomplete() # The line below finds the command in $commands using grep # Then, using sed, it removes everything from the found command before the --flags (e.g. "command:subcommand:subsubcom --flag1 --flag2" -> "--flag1 --flag2") opts=$(printf "%s " "\${commands[@]}" | grep "\${normalizedCommand}" | sed -n "s/^\${normalizedCommand} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi - COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + fi } complete -F __autocomplete diff --git a/src/autocomplete/bash.ts b/src/autocomplete/bash.ts index a1de4d90..1b220731 100644 --- a/src/autocomplete/bash.ts +++ b/src/autocomplete/bash.ts @@ -10,8 +10,35 @@ __autocomplete() " +local orgs=" + +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ \${COMP_WORDS[2]} == ":" ]]; then @@ -22,9 +49,16 @@ __autocomplete() __COMP_WORDS="\${COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "\${__COMP_WORDS}" | sed -n "s/^\${__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + fi __ltrim_colon_completions "$cur" return 0 diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index cd3d7776..aa6389b2 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -1,4 +1,5 @@ import makeDebug from 'debug' +import {execSync} from 'node:child_process' import {mkdir, writeFile} from 'node:fs/promises' import * as path from 'node:path' @@ -66,6 +67,7 @@ export default class Create extends AutocompleteBase { ) .replaceAll('', cliBin) .replaceAll('', this.bashCommandsWithFlagsList) + .replaceAll('', this.bashOrgs) ) } @@ -79,6 +81,10 @@ export default class Create extends AutocompleteBase { return path.join(this.autocompleteCacheDir, 'functions', 'bash') } + private get bashOrgs(): string { + return this.orgs.join('\n') + } + private get bashSetupScript(): string { const setup = path.join(this.bashFunctionsDir, `${this.cliBin}.bash`) const bin = this.cliBinEnvVar @@ -206,6 +212,17 @@ export default class Create extends AutocompleteBase { .join('\n') } + private get orgs(): string[] { + const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const result: string[] = [] + for (const element of orgsJson.result) { + if (element.alias) result.push(element.alias) + else result.push(element.username) + } + + return result + } + private get pwshCompletionFunctionPath(): string { // /autocomplete/functions/powershell/.ps1 return path.join(this.pwshFunctionsDir, `${this.cliBin}.ps1`) From b453c8947aaebd2e01b175dd4b1f413004012149 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:45:06 +0100 Subject: [PATCH 03/25] feat: refine zsh version for colon-separated commands --- src/autocomplete/zsh.ts | 27 ++++++++++++++++++++--- src/commands/autocomplete/create.ts | 33 ++++++++++++++++------------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 0ee98334..932b121b 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -1,5 +1,6 @@ import {Command, Config, Interfaces} from '@oclif/core' import * as ejs from 'ejs' +import {execSync} from 'node:child_process' import * as util from 'node:util' const argTemplate = ' "%s")\n %s\n ;;\n' @@ -26,12 +27,15 @@ export default class ZshCompWithSpaces { private commands: CommandCompletion[] + private orgs: string[] + private topics: Topic[] constructor(config: Config) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() + this.orgs = this.getOrgs() } public generate(): string { @@ -85,10 +89,12 @@ export default class ZshCompWithSpaces { ${this.config.binAliases?.map((a) => `compdef ${a}=${this.config.bin}`).join('\n') ?? ''} _orgs(){ - local completions - completions=($(sf autocomplete --orgs)) + local orgs + orgs=( + ${this.genOrgs()} + ) - _describe -t completions 'completions' completions && return 0 + _describe -t orgs 'orgs' orgs && return 0 } ${this.topics.map((t) => this.genZshTopicCompFun(t.name)).join('\n')} @@ -131,6 +137,10 @@ _${this.config.bin} return this._coTopics } + private genOrgs(): string { + return this.orgs.join('\n') + } + private genZshFlagArgumentsBlock(flags?: CommandFlags): string { // if a command doesn't have flags make it only complete files // also add comp for the global `--help` flag. @@ -394,6 +404,17 @@ _${this.config.bin} return cmds } + private getOrgs(): string[] { + const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const result: string[] = [] + for (const element of orgsJson.result) { + if (element.alias) result.push(element.alias) + else result.push(element.username) + } + + return result + } + private getTopics(): Topic[] { const topics = this.config.topics .filter((topic: Interfaces.Topic) => { diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index aa6389b2..fa92d2cc 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -36,6 +36,8 @@ export default class Create extends AutocompleteBase { private _commands?: CommandCompletion[] + private orgs: string[] = this.getOrgs() + async run() { // 1. ensure needed dirs await this.ensureDirs() @@ -67,7 +69,7 @@ export default class Create extends AutocompleteBase { ) .replaceAll('', cliBin) .replaceAll('', this.bashCommandsWithFlagsList) - .replaceAll('', this.bashOrgs) + .replaceAll('', this.genOrgs) ) } @@ -81,10 +83,6 @@ export default class Create extends AutocompleteBase { return path.join(this.autocompleteCacheDir, 'functions', 'bash') } - private get bashOrgs(): string { - return this.orgs.join('\n') - } - private get bashSetupScript(): string { const setup = path.join(this.bashFunctionsDir, `${this.cliBin}.bash`) const bin = this.cliBinEnvVar @@ -196,6 +194,10 @@ export default class Create extends AutocompleteBase { .join(' ') } + private get genOrgs(): string { + return this.orgs.join('\n') + } + private genZshFlagSpecs(Klass: any): string { return Object.keys(Klass.flags || {}) .filter((flag) => Klass.flags && !Klass.flags[flag].hidden) @@ -205,14 +207,19 @@ export default class Create extends AutocompleteBase { const isOption = f.type === 'option' const name = isBoolean ? flag : `${flag}=-` const multiple = isOption && f.multiple ? '*' : '' - const valueCmpl = isBoolean ? '' : ':' + let valueCmpl = isBoolean ? '' : ':' + + if (name === 'target-org=-') { + valueCmpl += `array_values:((\$\{_orgs[*]\}))` + } + const completion = `${multiple}--${name}[${sanitizeDescription(f.summary || f.description)}]${valueCmpl}` return `"${completion}"` }) .join('\n') } - private get orgs(): string[] { + private getOrgs(): string[] { const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) const result: string[] = [] for (const element of orgsJson.result) { @@ -238,21 +245,17 @@ export default class Create extends AutocompleteBase { const {cliBin} = this const allCommandsMeta = this.genAllCommandsMetaString const caseStatementForFlagsMeta = this.genCaseStatementForFlagsMetaString + const orgs = this.genOrgs return `#compdef ${cliBin} -_orgs(){ - local completions - completions=($(sf autocomplete --orgs)) - - _describe -t completions 'completions' completions && return 0 -} - - _${cliBin} () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} local -a _command_flags=() + local -a _orgs=( +${orgs} +) ## public cli commands & flags local -a _all_commands=( From 3fb293abed0f023a44d5365c6a7ff6932da9f7db Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:49:22 +0100 Subject: [PATCH 04/25] feat: remove unecessary code from index --- src/commands/autocomplete/index.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index 1b73ee03..fa73074d 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -1,6 +1,5 @@ import {Args, Flags, ux} from '@oclif/core' import chalk from 'chalk' -import {execSync} from 'node:child_process' import {EOL} from 'node:os' import {AutocompleteBase} from '../../base.js' @@ -50,7 +49,6 @@ export default class Index extends AutocompleteBase { ] static flags = { - orgs: Flags.boolean({char: 'o', description: 'Get authenticated orgs'}), 'refresh-cache': Flags.boolean({char: 'r', description: 'Refresh cache (ignores displaying instructions)'}), } @@ -64,18 +62,6 @@ export default class Index extends AutocompleteBase { ) } - // Build the current list of authenticated orgs - if (flags.orgs) { - const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) - let result: string = '' - for (const element of orgsJson.result) { - result += (element.alias ? element.alias + ':' + element.username : element.username + ':') + '\n' - } - - this.log(result) - this.exit() - } - ux.action.start(`${chalk.bold('Building the autocomplete cache')}`) await Create.run([], this.config) ux.action.stop() From cd7d261c8468b97072b7635ac97e925a5728c46c Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:02:17 +0100 Subject: [PATCH 05/25] feat: powershell version --- src/autocomplete/powershell.ts | 60 ++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 287f2151..5bfe0ff0 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -1,5 +1,6 @@ import {Command, Config, Interfaces} from '@oclif/core' import * as ejs from 'ejs' +import {execSync} from 'node:child_process' import {EOL} from 'node:os' import * as util from 'node:util' @@ -25,12 +26,15 @@ export default class PowerShellComp { private commands: CommandCompletion[] + private orgs: string[] + private topics: Topic[] constructor(config: Config) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() + this.orgs = this.getOrgs() } public generate(): string { @@ -126,6 +130,9 @@ ${hashtables.join('\n')} using namespace System.Management.Automation using namespace System.Management.Automation.Language +$orgs = @{ + ${this.genOrgs()} +} $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -194,6 +201,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if($CurrentLine[-1] -like "--target-org*"){ + $search = $CurrentLine[-1].Substring('--target-org '.Length) + $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -206,21 +219,28 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' + $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { @@ -368,6 +388,15 @@ ${flaghHashtables.join('\n')} return leafTpl } + private genOrgs(): string { + let jointedOrgs = '' + for (const org of this.orgs) { + jointedOrgs += `"${org}" = @{}\n` + } + + return jointedOrgs + } + private getCommands(): CommandCompletion[] { const cmds: CommandCompletion[] = [] @@ -415,6 +444,17 @@ ${flaghHashtables.join('\n')} return cmds } + private getOrgs(): string[] { + const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const result: string[] = [] + for (const element of orgsJson.result) { + if (element.alias) result.push(element.alias) + else result.push(element.username) + } + + return result.sort() + } + private getTopics(): Topic[] { const topics = this.config.topics .filter((topic: Interfaces.Topic) => { From 52d727e0b7ee53487885c483ee37a124f9e49a80 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:19:46 +0100 Subject: [PATCH 06/25] feat: sort org list for all shells --- src/autocomplete/zsh.ts | 2 +- src/commands/autocomplete/create.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 932b121b..c5c08fc5 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -412,7 +412,7 @@ _${this.config.bin} else result.push(element.username) } - return result + return result.sort() } private getTopics(): Topic[] { diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index fa92d2cc..b50161e3 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -227,7 +227,7 @@ export default class Create extends AutocompleteBase { else result.push(element.username) } - return result + return result.sort() } private get pwshCompletionFunctionPath(): string { From 82dd62e9a12799cb46f69af2facdc8c9776d4e42 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:25:08 +0100 Subject: [PATCH 07/25] feat: remove warnings from cli when retrieving orgs --- src/autocomplete/powershell.ts | 2 +- src/autocomplete/zsh.ts | 2 +- src/commands/autocomplete/create.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 5bfe0ff0..32fd3b8f 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -445,7 +445,7 @@ ${flaghHashtables.join('\n')} } private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const orgsJson = JSON.parse(execSync('sf org list auth --json 2>null').toString()) const result: string[] = [] for (const element of orgsJson.result) { if (element.alias) result.push(element.alias) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index c5c08fc5..31a3e886 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -405,7 +405,7 @@ _${this.config.bin} } private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const orgsJson = JSON.parse(execSync('sf org list auth --json 2>/dev/null').toString()) const result: string[] = [] for (const element of orgsJson.result) { if (element.alias) result.push(element.alias) diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index b50161e3..be2e200e 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -220,7 +220,7 @@ export default class Create extends AutocompleteBase { } private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json').toString()) + const orgsJson = JSON.parse(execSync('sf org list auth --json 2>/dev/null').toString()) const result: string[] = [] for (const element of orgsJson.result) { if (element.alias) result.push(element.alias) From 5c4159e04d1fecfab000b55735c4c9dba54c5461 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:49:02 +0100 Subject: [PATCH 08/25] test: tests #bashCompletionFunction and #bashCompletionFunction with spaces --- src/commands/autocomplete/create.ts | 11 ++- test/commands/autocomplete/create.test.ts | 115 ++++++++++++++++++---- test/helpers/orgruntest.ts | 1 + 3 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 test/helpers/orgruntest.ts diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index be2e200e..9ef1775f 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -1,3 +1,4 @@ +import {Config} from '@oclif/core' import makeDebug from 'debug' import {execSync} from 'node:child_process' import {mkdir, writeFile} from 'node:fs/promises' @@ -36,7 +37,13 @@ export default class Create extends AutocompleteBase { private _commands?: CommandCompletion[] - private orgs: string[] = this.getOrgs() + private orgs: string[] + + constructor(argv: string[], config: Config, orgs?: string[]) { + super(argv, config) + + this.orgs = orgs || this.getOrgs() + } async run() { // 1. ensure needed dirs @@ -255,7 +262,7 @@ _${cliBin} () { local -a _command_flags=() local -a _orgs=( ${orgs} -) +) ## public cli commands & flags local -a _all_commands=( diff --git a/test/commands/autocomplete/create.test.ts b/test/commands/autocomplete/create.test.ts index 2e40073a..3aff94f7 100644 --- a/test/commands/autocomplete/create.test.ts +++ b/test/commands/autocomplete/create.test.ts @@ -10,6 +10,7 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../. const config = new Config({root}) // autocomplete will throw error on windows ci +import {testOrgs} from '../../helpers/orgruntest.js' import {default as skipWindows} from '../../helpers/runtest.js' skipWindows('Create', () => { @@ -19,7 +20,7 @@ skipWindows('Create', () => { let plugin: any before(async () => { await config.load() - cmd = new Create([], config) + cmd = new Create([], config, testOrgs) plugin = new Plugin({root}) cmd.config.plugins = [plugin] plugin._manifest = () => @@ -77,8 +78,37 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " +local orgs=" +org1alias +org2.username@org.com +org3alias +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + + if [[ "$cur" != "-"* ]]; then + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ \${COMP_WORDS[2]} == ":" ]]; then @@ -89,9 +119,16 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json __COMP_WORDS="\${COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "\${__COMP_WORDS}" | sed -n "s/^\${__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) ) + fi __ltrim_colon_completions "$cur" return 0 @@ -106,7 +143,7 @@ complete -o default -F _oclif-example_autocomplete oclif-example\n`) await spacedConfig.load() spacedConfig.topicSeparator = ' ' // : any is required for the next two lines otherwise ts will complain about _manifest and bashCompletionFunction being private down below - const spacedCmd: any = new Create([], spacedConfig) + const spacedCmd: any = new Create([], spacedConfig, testOrgs) const spacedPlugin: any = new Plugin({root}) spacedCmd.config.plugins = [spacedPlugin] spacedPlugin._manifest = () => @@ -131,6 +168,31 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " +local orgs=" +org1alias +org2.username@org.com +org3alias +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + function __trim_colon_commands() { # Turn $commands into an array @@ -155,26 +217,31 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json } if [[ "$cur" != "-"* ]]; then - # Command - __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else - # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") - normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" + # Command + __COMP_WORDS=( "\${COMP_WORDS[@]:1}" ) - # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") - colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + # The command typed by the user but separated by colons (e.g. "mycli command subcom" -> "command:subcom") + normalizedCommand="$( printf "%s" "$(join_by ":" "\${__COMP_WORDS[@]}")" )" - if [[ -z "$normalizedCommand" ]]; then - # If there is no normalizedCommand yet the user hasn't typed in a full command - # So we should trim all subcommands & flags from $commands so we can suggest all top level commands - opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') - else - # Filter $commands to just the ones that match the $normalizedCommand and turn into an array - commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) - # Trim higher level and subcommands from the subcommands to suggest - __trim_colon_commands "$colonPrefix" + # The command hirarchy, with colons, leading up to the last subcommand entered (e.g. "mycli com subcommand subsubcom" -> "com:subcommand:") + colonPrefix="\${normalizedCommand%"\${normalizedCommand##*:}"}" + + if [[ -z "$normalizedCommand" ]]; then + # If there is no normalizedCommand yet the user hasn't typed in a full command + # So we should trim all subcommands & flags from $commands so we can suggest all top level commands + opts=$(printf "%s " "\${commands[@]}" | grep -Eo '^[a-zA-Z0-9_-]+') + else + # Filter $commands to just the ones that match the $normalizedCommand and turn into an array + commands=( $(compgen -W "$commands" -- "\${normalizedCommand}") ) + # Trim higher level and subcommands from the subcommands to suggest + __trim_colon_commands "$colonPrefix" - opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + opts=$(printf "%s " "\${commands[@]}") # | grep -Eo '^[a-zA-Z0-9_-]+' + fi fi ${'else '} # Flag @@ -186,9 +253,15 @@ foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json # The line below finds the command in $commands using grep # Then, using sed, it removes everything from the found command before the --flags (e.g. "command:subcommand:subsubcom --flag1 --flag2" -> "--flag1 --flag2") opts=$(printf "%s " "\${commands[@]}" | grep "\${normalizedCommand}" | sed -n "s/^\${normalizedCommand} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi - COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=($(compgen -W "$opts" -- "\${cur}")) + fi } complete -F _oclif-example_autocomplete oclif-example\n`) diff --git a/test/helpers/orgruntest.ts b/test/helpers/orgruntest.ts new file mode 100644 index 00000000..376e7430 --- /dev/null +++ b/test/helpers/orgruntest.ts @@ -0,0 +1 @@ +export const testOrgs = ['org1alias', 'org2.username@org.com', 'org3alias'] From 5769b1d17e261ce0f9e5eb57c009df1aa2baa31e Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:59:39 +0100 Subject: [PATCH 09/25] test: bash generates a valid completion file --- src/autocomplete/bash-spaces.ts | 33 +++++++++++++------------ test/autocomplete/bash.test.ts | 43 ++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/autocomplete/bash-spaces.ts b/src/autocomplete/bash-spaces.ts index f6ddba30..921108dc 100644 --- a/src/autocomplete/bash-spaces.ts +++ b/src/autocomplete/bash-spaces.ts @@ -14,27 +14,28 @@ __autocomplete() " - local orgs=" +local orgs=" " - local targetOrgFlags=("--target-org" "-o") - function _isTargetOrgFlag(){ - local value="$1" - for flag in "\${targetOrgFlags[@]}"; do - if [[ "$flag" == "$value" ]]; then - return 0 # value found - fi - done - return 1 # value not found - } +local targetOrgFlags=("--target-org" "-o") - function _suggestOrgs(){ - if [[ "$cur" != "-"* ]]; then - opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") - COMPREPLY=($(compgen -W "$opts")) +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found fi - } + done + return 1 # value not found +} + +function _suggestOrgs(){ + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} function __trim_colon_commands() { diff --git a/test/autocomplete/bash.test.ts b/test/autocomplete/bash.test.ts index d14331a2..0ab46b5e 100644 --- a/test/autocomplete/bash.test.ts +++ b/test/autocomplete/bash.test.ts @@ -5,6 +5,7 @@ import {expect} from 'chai' import Create from '../../src/commands/autocomplete/create.js' // autocomplete will throw error on windows ci +import {testOrgs} from '../helpers/orgruntest.js' import {default as skipWindows} from '../helpers/runtest.js' import {fileURLToPath} from 'node:url' @@ -186,7 +187,7 @@ skipWindows('bash comp', () => { it('generates a valid completion file.', async () => { config.bin = 'test-cli' - const create = new Create([], config) + const create = new Create([], config, testOrgs) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore @@ -206,8 +207,37 @@ ${'search '} ${'app:execute:code '} " +local orgs=" +org1alias +org2.username@org.com +org3alias +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + + if [[ "$cur" != "-"* ]]; then + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -218,9 +248,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 From a846c01c52a81b864ec2c35ae270747169dafb52 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:00:21 +0100 Subject: [PATCH 10/25] test: bash generates a valid completion file with an alias --- test/autocomplete/bash.test.ts | 42 +++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/autocomplete/bash.test.ts b/test/autocomplete/bash.test.ts index 0ab46b5e..6a331ff9 100644 --- a/test/autocomplete/bash.test.ts +++ b/test/autocomplete/bash.test.ts @@ -268,7 +268,7 @@ complete -o default -F _test-cli_autocomplete test-cli`) it('generates a valid completion file with an alias.', async () => { config.bin = 'test-cli' config.binAliases = ['alias'] - const create = new Create([], config) + const create = new Create([], config, testOrgs) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore expect(create.bashCompletionFunction.trim()).to.equal(`#!/usr/bin/env bash @@ -287,8 +287,37 @@ ${'search '} ${'app:execute:code '} " +local orgs=" +org1alias +org2.username@org.com +org3alias +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ + if [[ "$cur" != "-"* ]]; then + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -299,9 +328,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 From eb9c41da8d35cdc2601355268fee02cac5cdd9a2 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:04:14 +0100 Subject: [PATCH 11/25] test: bash generates a valid completion file with multiple aliases --- test/autocomplete/bash.test.ts | 42 +++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/autocomplete/bash.test.ts b/test/autocomplete/bash.test.ts index 6a331ff9..440bbaaf 100644 --- a/test/autocomplete/bash.test.ts +++ b/test/autocomplete/bash.test.ts @@ -349,7 +349,7 @@ complete -F _test-cli_autocomplete alias`) it('generates a valid completion file with multiple aliases.', async () => { config.bin = 'test-cli' config.binAliases = ['alias', 'alias2'] - const create = new Create([], config) + const create = new Create([], config, testOrgs) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore expect(create.bashCompletionFunction).to.equal(`#!/usr/bin/env bash @@ -368,8 +368,37 @@ ${'search '} ${'app:execute:code '} " +local orgs=" +org1alias +org2.username@org.com +org3alias +" + +local targetOrgFlags=("--target-org" "-o") + +function _isTargetOrgFlag(){ + local value="$1" + for flag in "\${targetOrgFlags[@]}"; do + if [[ "$flag" == "$value" ]]; then + return 0 # value found + fi + done + return 1 # value not found +} + +function _suggestOrgs(){ if [[ "$cur" != "-"* ]]; then - opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") + COMPREPLY=($(compgen -W "$opts")) + fi +} + + if [[ "$cur" != "-"* ]]; then + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD-1]}"; then + _suggestOrgs + else + opts=$(printf "$commands" | grep -Eo '^[a-zA-Z0-9:_-]+') + fi else local __COMP_WORDS if [[ $\{COMP_WORDS[2]} == ":" ]]; then @@ -380,9 +409,16 @@ ${'app:execute:code '} __COMP_WORDS="$\{COMP_WORDS[@]:1:1}" fi opts=$(printf "$commands" | grep "$\{__COMP_WORDS}" | sed -n "s/^$\{__COMP_WORDS} //p") + + if _isTargetOrgFlag "\${COMP_WORDS[COMP_CWORD]}"; then + _suggestOrgs + fi fi _get_comp_words_by_ref -n : cur - COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + + if [[ -z "$COMPREPLY" ]]; then + COMPREPLY=( $(compgen -W "$\{opts}" -- $\{cur}) ) + fi __ltrim_colon_completions "$cur" return 0 From b9d0fb889038c0b27e9ce7b3238b06ac0e732ce2 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:58:34 +0100 Subject: [PATCH 12/25] test: #zshCompletionFunction --- test/commands/autocomplete/create.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/commands/autocomplete/create.test.ts b/test/commands/autocomplete/create.test.ts index 3aff94f7..1db61675 100644 --- a/test/commands/autocomplete/create.test.ts +++ b/test/commands/autocomplete/create.test.ts @@ -275,6 +275,11 @@ _oclif-example () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} local -a _command_flags=() + local -a _orgs=( +org1alias +org2.username@org.com +org3alias +) ## public cli commands & flags local -a _all_commands=( From c6ed843bf6138bbc97de9292a715a6cf0ecb0164 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:22:15 +0100 Subject: [PATCH 13/25] test: zsh generates a valid completion file --- src/autocomplete/zsh.ts | 4 ++-- test/autocomplete/zsh.test.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 31a3e886..48645438 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -31,11 +31,11 @@ export default class ZshCompWithSpaces { private topics: Topic[] - constructor(config: Config) { + constructor(config: Config, orgs?: string[]) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() - this.orgs = this.getOrgs() + this.orgs = orgs || this.getOrgs() } public generate(): string { diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index 53e3c821..4db0ed6a 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -6,6 +6,7 @@ import {fileURLToPath} from 'node:url' import ZshCompWithSpaces from '../../src/autocomplete/zsh.js' // autocomplete will throw error on windows ci +import {testOrgs} from '../helpers/orgruntest.js' import {default as skipWindows} from '../helpers/runtest.js' class MyCommandClass implements Command.Cached { @@ -208,10 +209,21 @@ skipWindows('zsh comp', () => { it('generates a valid completion file.', () => { config.bin = 'test-cli' - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli +_orgs(){ + local orgs + orgs=( + org1alias +org2.username@org.com +org3alias + ) + + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args From 42f4699ff98137fedbecfc525218d3a1baf82902 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:37:19 +0100 Subject: [PATCH 14/25] test: generates a valid completion file with a bin alias --- src/autocomplete/zsh.ts | 2 +- test/autocomplete/zsh.test.ts | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 48645438..f304ef47 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -93,7 +93,7 @@ _orgs(){ orgs=( ${this.genOrgs()} ) - + _describe -t orgs 'orgs' orgs && return 0 } diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index 4db0ed6a..f14ee548 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -220,7 +220,7 @@ _orgs(){ org2.username@org.com org3alias ) - + _describe -t orgs 'orgs' orgs && return 0 } @@ -353,10 +353,21 @@ _test-cli it('generates a valid completion file with a bin alias.', () => { config.bin = 'test-cli' config.binAliases = ['testing'] - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli compdef testing=test-cli +_orgs(){ + local orgs + orgs=( + org1alias +org2.username@org.com +org3alias + ) + + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args From 5615e3c2808db83bfe839ea15af1383f1adde2e8 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:39:14 +0100 Subject: [PATCH 15/25] test: generates a valid completion file with multiple bin aliases --- test/autocomplete/zsh.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index f14ee548..22a34ca1 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -497,11 +497,22 @@ _test-cli it('generates a valid completion file with multiple bin aliases.', () => { config.bin = 'test-cli' config.binAliases = ['testing', 'testing2'] - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli compdef testing=test-cli compdef testing2=test-cli +_orgs(){ + local orgs + orgs=( + org1alias +org2.username@org.com +org3alias + ) + + _describe -t orgs 'orgs' orgs && return 0 +} + _test-cli_app() { local context state state_descr line typeset -A opt_args From 418e3a7759e88d035af6e6e7bb18e607d5e992c3 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:47:22 +0100 Subject: [PATCH 16/25] test: powershell generates a valid completion file --- src/autocomplete/powershell.ts | 7 +++-- test/autocomplete/powershell.test.ts | 43 +++++++++++++++++++++------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 32fd3b8f..44c40663 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -30,11 +30,11 @@ export default class PowerShellComp { private topics: Topic[] - constructor(config: Config) { + constructor(config: Config, orgs?: string[]) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() - this.orgs = this.getOrgs() + this.orgs = orgs || this.getOrgs() } public generate(): string { @@ -133,6 +133,7 @@ using namespace System.Management.Automation.Language $orgs = @{ ${this.genOrgs()} } + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -219,7 +220,7 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } - } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. diff --git a/test/autocomplete/powershell.test.ts b/test/autocomplete/powershell.test.ts index e83a8253..570d3c94 100644 --- a/test/autocomplete/powershell.test.ts +++ b/test/autocomplete/powershell.test.ts @@ -5,6 +5,7 @@ import * as path from 'node:path' import {fileURLToPath} from 'node:url' import PowerShellComp from '../../src/autocomplete/powershell.js' +import {testOrgs} from '../helpers/orgruntest.js' class MyCommandClass implements Command.Cached { [key: string]: unknown @@ -202,11 +203,18 @@ describe('powershell completion', () => { it('generates a valid completion file.', () => { config.bin = 'test-cli' - const powerShellComp = new PowerShellComp(config as Config) + const powerShellComp = new PowerShellComp(config as Config, testOrgs) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language +$orgs = @{ + "org1alias" = @{} +"org2.username@org.com" = @{} +"org3alias" = @{} + +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -338,6 +346,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if($CurrentLine[-1] -like "--target-org*"){ + $search = $CurrentLine[-1].Substring('--target-org '.Length) + $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -350,21 +364,28 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' + $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { From 93bb0a98c00ebd046ec08e867bdd5da0904fe97c Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:50:33 +0100 Subject: [PATCH 17/25] test: powershell generates a valid completion file with a bin alias --- test/autocomplete/powershell.test.ts | 42 ++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/test/autocomplete/powershell.test.ts b/test/autocomplete/powershell.test.ts index 570d3c94..1f4e6436 100644 --- a/test/autocomplete/powershell.test.ts +++ b/test/autocomplete/powershell.test.ts @@ -415,11 +415,18 @@ Register-ArgumentCompleter -Native -CommandName test-cli -ScriptBlock $scriptblo it('generates a valid completion file with a bin alias.', () => { config.bin = 'test-cli' config.binAliases = ['test'] - const powerShellComp = new PowerShellComp(config as Config) + const powerShellComp = new PowerShellComp(config as Config, testOrgs) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language +$orgs = @{ + "org1alias" = @{} +"org2.username@org.com" = @{} +"org3alias" = @{} + +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -551,6 +558,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if($CurrentLine[-1] -like "--target-org*"){ + $search = $CurrentLine[-1].Substring('--target-org '.Length) + $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -563,21 +576,28 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' + $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { From d73229aa5590cc1adb11745a753aee697dbc4fc5 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:52:20 +0100 Subject: [PATCH 18/25] test: powershell generates a valid completion file with multiple bin aliases --- test/autocomplete/powershell.test.ts | 42 ++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/test/autocomplete/powershell.test.ts b/test/autocomplete/powershell.test.ts index 1f4e6436..441279dd 100644 --- a/test/autocomplete/powershell.test.ts +++ b/test/autocomplete/powershell.test.ts @@ -627,11 +627,18 @@ Register-ArgumentCompleter -Native -CommandName @("test","test-cli") -ScriptBloc it('generates a valid completion file with multiple bin aliases.', () => { config.bin = 'test-cli' config.binAliases = ['test', 'test1'] - const powerShellComp = new PowerShellComp(config as Config) + const powerShellComp = new PowerShellComp(config as Config, testOrgs) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language +$orgs = @{ + "org1alias" = @{} +"org2.username@org.com" = @{} +"org3alias" = @{} + +} + $scriptblock = { param($WordToComplete, $CommandAst, $CursorPosition) @@ -763,6 +770,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { + if($CurrentLine[-1] -like "--target-org*"){ + $search = $CurrentLine[-1].Substring('--target-org '.Length) + $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { # Filter out already used flags (unless \`flag.multiple = true\`). @@ -775,21 +788,28 @@ $scriptblock = { "ParameterValue", "$($NextArg._command.flags[$_.Key].summary ?? " ")" } + } } else { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") - if ($NextArg.keys -gt 0) { - $NextArg.GetEnumerator() | Where-Object { - $_.Key.StartsWith("$WordToComplete") - } | Sort-Object -Property key | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList \` - $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), - $_.Key, - "ParameterValue", - "$($NextArg[$_.Key]._summary ?? " ")" - } + if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' + $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } + }else{ + if ($NextArg.keys -gt 0) { + $NextArg.GetEnumerator() | Where-Object { + $_.Key.StartsWith("$WordToComplete") + } | Sort-Object -Property key | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList \` + $($Mode -eq "MenuComplete" ? "$($_.Key) " : "$($_.Key)"), + $_.Key, + "ParameterValue", + "$($NextArg[$_.Key]._summary ?? " ")" + } + } } } } else { From 38ffe82514c83cfa3f364dec7e32aaf04685d041 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:54:07 +0100 Subject: [PATCH 19/25] fix: use $null instead of null to mask warnings --- src/autocomplete/powershell.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 44c40663..9432f304 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -446,7 +446,7 @@ ${flaghHashtables.join('\n')} } private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json 2>null').toString()) + const orgsJson = JSON.parse(execSync('sf org list auth --json 2>$null').toString()) const result: string[] = [] for (const element of orgsJson.result) { if (element.alias) result.push(element.alias) From c2c6c417f74022640b1ca7aee52d22a4ee9cf074 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:21:31 +0100 Subject: [PATCH 20/25] feat: use a new flag to display up-to-date dynamic org list, but have a delay to get the org list --- src/autocomplete/bash-spaces.ts | 6 +-- src/autocomplete/bash.ts | 6 +-- src/autocomplete/powershell.ts | 83 +++++++++++++++++------------ src/autocomplete/zsh.ts | 9 ++-- src/commands/autocomplete/create.ts | 37 +++---------- src/commands/autocomplete/index.ts | 71 ++++++++++++++++-------- 6 files changed, 111 insertions(+), 101 deletions(-) diff --git a/src/autocomplete/bash-spaces.ts b/src/autocomplete/bash-spaces.ts index 921108dc..fc365d05 100644 --- a/src/autocomplete/bash-spaces.ts +++ b/src/autocomplete/bash-spaces.ts @@ -14,10 +14,6 @@ __autocomplete() " -local orgs=" - -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -31,6 +27,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) diff --git a/src/autocomplete/bash.ts b/src/autocomplete/bash.ts index 1b220731..432310ed 100644 --- a/src/autocomplete/bash.ts +++ b/src/autocomplete/bash.ts @@ -10,10 +10,6 @@ __autocomplete() " -local orgs=" - -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -27,6 +23,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 9432f304..1457b2fd 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -1,6 +1,5 @@ import {Command, Config, Interfaces} from '@oclif/core' import * as ejs from 'ejs' -import {execSync} from 'node:child_process' import {EOL} from 'node:os' import * as util from 'node:util' @@ -26,15 +25,12 @@ export default class PowerShellComp { private commands: CommandCompletion[] - private orgs: string[] - private topics: Topic[] - constructor(config: Config, orgs?: string[]) { + constructor(config: Config) { this.config = config this.topics = this.getTopics() this.commands = this.getCommands() - this.orgs = orgs || this.getOrgs() } public generate(): string { @@ -130,8 +126,43 @@ ${hashtables.join('\n')} using namespace System.Management.Automation using namespace System.Management.Automation.Language -$orgs = @{ - ${this.genOrgs()} +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" } $scriptblock = { @@ -202,11 +233,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { - if($CurrentLine[-1] -like "--target-org*"){ - $search = $CurrentLine[-1].Substring('--target-org '.Length) - $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' - } + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { @@ -225,10 +257,11 @@ $scriptblock = { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags - if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' - $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' } }else{ if ($NextArg.keys -gt 0) { @@ -389,15 +422,6 @@ ${flaghHashtables.join('\n')} return leafTpl } - private genOrgs(): string { - let jointedOrgs = '' - for (const org of this.orgs) { - jointedOrgs += `"${org}" = @{}\n` - } - - return jointedOrgs - } - private getCommands(): CommandCompletion[] { const cmds: CommandCompletion[] = [] @@ -445,17 +469,6 @@ ${flaghHashtables.join('\n')} return cmds } - private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json 2>$null').toString()) - const result: string[] = [] - for (const element of orgsJson.result) { - if (element.alias) result.push(element.alias) - else result.push(element.username) - } - - return result.sort() - } - private getTopics(): Topic[] { const topics = this.config.topics .filter((topic: Interfaces.Topic) => { diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index f304ef47..1930c6c7 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -89,12 +89,11 @@ export default class ZshCompWithSpaces { ${this.config.binAliases?.map((a) => `compdef ${a}=${this.config.bin}`).join('\n') ?? ''} _orgs(){ - local orgs - orgs=( - ${this.genOrgs()} - ) + if [[ -z $ORGS ]]; then + export ORGS=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + fi - _describe -t orgs 'orgs' orgs && return 0 + _describe -t ORGS 'ORGS' ORGS && return 0 } ${this.topics.map((t) => this.genZshTopicCompFun(t.name)).join('\n')} diff --git a/src/commands/autocomplete/create.ts b/src/commands/autocomplete/create.ts index 9ef1775f..d76036d1 100644 --- a/src/commands/autocomplete/create.ts +++ b/src/commands/autocomplete/create.ts @@ -1,6 +1,4 @@ -import {Config} from '@oclif/core' import makeDebug from 'debug' -import {execSync} from 'node:child_process' import {mkdir, writeFile} from 'node:fs/promises' import * as path from 'node:path' @@ -37,14 +35,6 @@ export default class Create extends AutocompleteBase { private _commands?: CommandCompletion[] - private orgs: string[] - - constructor(argv: string[], config: Config, orgs?: string[]) { - super(argv, config) - - this.orgs = orgs || this.getOrgs() - } - async run() { // 1. ensure needed dirs await this.ensureDirs() @@ -76,7 +66,6 @@ export default class Create extends AutocompleteBase { ) .replaceAll('', cliBin) .replaceAll('', this.bashCommandsWithFlagsList) - .replaceAll('', this.genOrgs) ) } @@ -201,10 +190,6 @@ export default class Create extends AutocompleteBase { .join(' ') } - private get genOrgs(): string { - return this.orgs.join('\n') - } - private genZshFlagSpecs(Klass: any): string { return Object.keys(Klass.flags || {}) .filter((flag) => Klass.flags && !Klass.flags[flag].hidden) @@ -217,7 +202,7 @@ export default class Create extends AutocompleteBase { let valueCmpl = isBoolean ? '' : ':' if (name === 'target-org=-') { - valueCmpl += `array_values:((\$\{_orgs[*]\}))` + valueCmpl += `array_values:((\${(@)$(_orgs)}))` } const completion = `${multiple}--${name}[${sanitizeDescription(f.summary || f.description)}]${valueCmpl}` @@ -226,17 +211,6 @@ export default class Create extends AutocompleteBase { .join('\n') } - private getOrgs(): string[] { - const orgsJson = JSON.parse(execSync('sf org list auth --json 2>/dev/null').toString()) - const result: string[] = [] - for (const element of orgsJson.result) { - if (element.alias) result.push(element.alias) - else result.push(element.username) - } - - return result.sort() - } - private get pwshCompletionFunctionPath(): string { // /autocomplete/functions/powershell/.ps1 return path.join(this.pwshFunctionsDir, `${this.cliBin}.ps1`) @@ -252,7 +226,6 @@ export default class Create extends AutocompleteBase { const {cliBin} = this const allCommandsMeta = this.genAllCommandsMetaString const caseStatementForFlagsMeta = this.genCaseStatementForFlagsMetaString - const orgs = this.genOrgs return `#compdef ${cliBin} @@ -260,10 +233,12 @@ _${cliBin} () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} local -a _command_flags=() - local -a _orgs=( -${orgs} -) + _orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + echo "$orgs" + } + ## public cli commands & flags local -a _all_commands=( ${allCommandsMeta} diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index fa73074d..247a22cd 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -1,6 +1,8 @@ import {Args, Flags, ux} from '@oclif/core' import chalk from 'chalk' +import {existsSync, readFileSync, readdirSync} from 'node:fs' import {EOL} from 'node:os' +import {default as path} from 'node:path' import {AutocompleteBase} from '../../base.js' import Create from './create.js' @@ -49,6 +51,7 @@ export default class Index extends AutocompleteBase { ] static flags = { + 'display-orgs': Flags.boolean({char: 'd', description: 'Display authenticated orgs.'}), 'refresh-cache': Flags.boolean({char: 'r', description: 'Refresh cache (ignores displaying instructions)'}), } @@ -62,37 +65,61 @@ export default class Index extends AutocompleteBase { ) } - ux.action.start(`${chalk.bold('Building the autocomplete cache')}`) - await Create.run([], this.config) - ux.action.stop() + if (flags['display-orgs']) { + const sfDir = path.join(this.config.home, '.sfdx') + const orgs: string[] = readdirSync(sfDir) + .filter((element) => element.match(/^.*@.*\.json/) !== null) + .map((element) => element.replace('.json', '')) + + let orgsAliases = [] + const aliasFilename = path.join(sfDir, 'alias.json') + if (existsSync(aliasFilename)) { + orgsAliases = JSON.parse(readFileSync(aliasFilename).toString()).orgs + for (const [alias, username] of Object.entries(orgsAliases)) { + const i = orgs.indexOf(username as string) + if (i > -1) { + orgs[i] = alias + } + } + } + + this.log( + orgs.join(` +`), + ) + } else { + ux.action.start(`${chalk.bold('Building the autocomplete cache')}`) + await Create.run([], this.config) + ux.action.stop() - if (!flags['refresh-cache']) { - const {bin} = this.config - const tabStr = shell === 'bash' ? '' : '' + if (!flags['refresh-cache']) { + const {bin} = this.config + const tabStr = shell === 'bash' ? '' : '' - const instructions = - shell === 'powershell' - ? `New-Item -Type Directory -Path (Split-Path -Parent $PROFILE) -ErrorAction SilentlyContinue -Add-Content -Path $PROFILE -Value (Invoke-Expression -Command "${bin} autocomplete${this.config.topicSeparator}script ${shell}"); .$PROFILE` - : `$ printf "eval $(${bin} autocomplete${this.config.topicSeparator}script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc` + const instructions = + shell === 'powershell' + ? `New-Item -Type Directory -Path (Split-Path -Parent $PROFILE) -ErrorAction SilentlyContinue + Add-Content -Path $PROFILE -Value (Invoke-Expression -Command "${bin} autocomplete${this.config.topicSeparator}script ${shell}"); .$PROFILE` + : `$ printf "eval $(${bin} autocomplete${this.config.topicSeparator}script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc` - const note = noteFromShell(shell) + const note = noteFromShell(shell) - this.log(` -${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)} + this.log(` + ${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)} -1) Add the autocomplete ${shell === 'powershell' ? 'file' : 'env var'} to your ${shell} profile and source it + 1) Add the autocomplete ${shell === 'powershell' ? 'file' : 'env var'} to your ${shell} profile and source it -${chalk.cyan(instructions)} + ${chalk.cyan(instructions)} -${chalk.bold('NOTE')}: ${note} + ${chalk.bold('NOTE')}: ${note} -2) Test it out, e.g.: -${chalk.cyan(`$ ${bin} ${tabStr}`)} # Command completion -${chalk.cyan(`$ ${bin} command --${tabStr}`)} # Flag completion + 2) Test it out, e.g.: + ${chalk.cyan(`$ ${bin} ${tabStr}`)} # Command completion + ${chalk.cyan(`$ ${bin} command --${tabStr}`)} # Flag completion -Enjoy! -`) + Enjoy! + `) + } } } } From 0f88fe04c9f56f50ad4dfccbd14c7f622305b05f Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:35:40 +0100 Subject: [PATCH 21/25] test: bash tests fixed --- test/autocomplete/bash.test.ts | 37 +++++++++++----------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/test/autocomplete/bash.test.ts b/test/autocomplete/bash.test.ts index 440bbaaf..63107da7 100644 --- a/test/autocomplete/bash.test.ts +++ b/test/autocomplete/bash.test.ts @@ -5,7 +5,6 @@ import {expect} from 'chai' import Create from '../../src/commands/autocomplete/create.js' // autocomplete will throw error on windows ci -import {testOrgs} from '../helpers/orgruntest.js' import {default as skipWindows} from '../helpers/runtest.js' import {fileURLToPath} from 'node:url' @@ -187,7 +186,7 @@ skipWindows('bash comp', () => { it('generates a valid completion file.', async () => { config.bin = 'test-cli' - const create = new Create([], config, testOrgs) + const create = new Create([], config) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore @@ -200,19 +199,13 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " -local orgs=" -org1alias -org2.username@org.com -org3alias -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -226,6 +219,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) @@ -268,7 +263,7 @@ complete -o default -F _test-cli_autocomplete test-cli`) it('generates a valid completion file with an alias.', async () => { config.bin = 'test-cli' config.binAliases = ['alias'] - const create = new Create([], config, testOrgs) + const create = new Create([], config) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore expect(create.bashCompletionFunction.trim()).to.equal(`#!/usr/bin/env bash @@ -280,19 +275,13 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " -local orgs=" -org1alias -org2.username@org.com -org3alias -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -306,6 +295,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) @@ -349,7 +340,7 @@ complete -F _test-cli_autocomplete alias`) it('generates a valid completion file with multiple aliases.', async () => { config.bin = 'test-cli' config.binAliases = ['alias', 'alias2'] - const create = new Create([], config, testOrgs) + const create = new Create([], config) // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore expect(create.bashCompletionFunction).to.equal(`#!/usr/bin/env bash @@ -361,19 +352,13 @@ _test-cli_autocomplete() COMPREPLY=() local commands=" -autocomplete --refresh-cache +autocomplete --display-orgs --refresh-cache deploy --metadata --api-version --json --ignore-errors deploy:functions --branch ${'search '} ${'app:execute:code '} " -local orgs=" -org1alias -org2.username@org.com -org3alias -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -387,6 +372,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) From 76b67b7f1a5b6d817000e7d75dff617aef99ea70 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:02:26 +0100 Subject: [PATCH 22/25] test: powershell tests fixed --- src/autocomplete/powershell.ts | 38 +++--- test/autocomplete/powershell.test.ts | 184 +++++++++++++++++++++------ 2 files changed, 163 insertions(+), 59 deletions(-) diff --git a/src/autocomplete/powershell.ts b/src/autocomplete/powershell.ts index 1457b2fd..b02bdd83 100644 --- a/src/autocomplete/powershell.ts +++ b/src/autocomplete/powershell.ts @@ -137,32 +137,32 @@ $targetOrgFlags=@{ } function containsTargetOrgFlag { - param ( - [Parameter(Mandatory=$true)] - [array]$items, + param ( + [Parameter(Mandatory=$true)] + [array]$items, - [Parameter(Mandatory=$true)] - [hashtable]$targetOrgFlags - ) + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) - foreach ($item in $items) { - if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} - } - return $false + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false } function getTargetOrgFlag { - param ( - [Parameter(Mandatory=$true)] - [array]$line, + param ( + [Parameter(Mandatory=$true)] + [array]$line, - [Parameter(Mandatory=$true)] - [hashtable]$targetOrgFlags - ) + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) - if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} - if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} - return "" + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" } $scriptblock = { diff --git a/test/autocomplete/powershell.test.ts b/test/autocomplete/powershell.test.ts index 441279dd..5c12a608 100644 --- a/test/autocomplete/powershell.test.ts +++ b/test/autocomplete/powershell.test.ts @@ -5,7 +5,6 @@ import * as path from 'node:path' import {fileURLToPath} from 'node:url' import PowerShellComp from '../../src/autocomplete/powershell.js' -import {testOrgs} from '../helpers/orgruntest.js' class MyCommandClass implements Command.Cached { [key: string]: unknown @@ -203,16 +202,48 @@ describe('powershell completion', () => { it('generates a valid completion file.', () => { config.bin = 'test-cli' - const powerShellComp = new PowerShellComp(config as Config, testOrgs) + const powerShellComp = new PowerShellComp(config as Config) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language -$orgs = @{ - "org1alias" = @{} -"org2.username@org.com" = @{} -"org3alias" = @{} +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" } $scriptblock = { @@ -267,6 +298,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -346,11 +378,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { - if($CurrentLine[-1] -like "--target-org*"){ - $search = $CurrentLine[-1].Substring('--target-org '.Length) - $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' - } + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { @@ -369,10 +402,11 @@ $scriptblock = { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags - if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' - $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' } }else{ if ($NextArg.keys -gt 0) { @@ -415,16 +449,48 @@ Register-ArgumentCompleter -Native -CommandName test-cli -ScriptBlock $scriptblo it('generates a valid completion file with a bin alias.', () => { config.bin = 'test-cli' config.binAliases = ['test'] - const powerShellComp = new PowerShellComp(config as Config, testOrgs) + const powerShellComp = new PowerShellComp(config as Config) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language -$orgs = @{ - "org1alias" = @{} -"org2.username@org.com" = @{} -"org3alias" = @{} +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" } $scriptblock = { @@ -479,6 +545,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -558,11 +625,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { - if($CurrentLine[-1] -like "--target-org*"){ - $search = $CurrentLine[-1].Substring('--target-org '.Length) - $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' - } + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { @@ -581,10 +649,11 @@ $scriptblock = { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags - if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' - $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' } }else{ if ($NextArg.keys -gt 0) { @@ -627,16 +696,48 @@ Register-ArgumentCompleter -Native -CommandName @("test","test-cli") -ScriptBloc it('generates a valid completion file with multiple bin aliases.', () => { config.bin = 'test-cli' config.binAliases = ['test', 'test1'] - const powerShellComp = new PowerShellComp(config as Config, testOrgs) + const powerShellComp = new PowerShellComp(config as Config) expect(powerShellComp.generate()).to.equal(` using namespace System.Management.Automation using namespace System.Management.Automation.Language -$orgs = @{ - "org1alias" = @{} -"org2.username@org.com" = @{} -"org3alias" = @{} +function orgs(){ + $orglist=sf autocomplete --display-orgs + return $orglist +} + +$targetOrgFlags=@{ + "long" = "--target-org" + "short" = "-o" +} + +function containsTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$items, + + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + foreach ($item in $items) { + if ($item -like $targetOrgFlags['long']+"*" -Or $item -like $targetOrgFlags['short']+"*") {return $true} + } + return $false +} + +function getTargetOrgFlag { + param ( + [Parameter(Mandatory=$true)] + [array]$line, + [Parameter(Mandatory=$true)] + [hashtable]$targetOrgFlags + ) + + if($line -contains $targetOrgFlags['long']) {return $targetOrgFlags['long']} + if($line -contains $targetOrgFlags['short']) {return $targetOrgFlags['short']} + return "" } $scriptblock = { @@ -691,6 +792,7 @@ $scriptblock = { "summary" = "Display autocomplete installation instructions." "flags" = @{ "help" = @{ "summary" = "Show help for command" } + "display-orgs" = @{ "summary" = "Display authenticated orgs." } "refresh-cache" = @{ "summary" = "Refresh cache (ignores displaying instructions)" } } } @@ -770,11 +872,12 @@ $scriptblock = { # Complete flags # \`cli config list -\` if ($WordToComplete -like '-*') { - if($CurrentLine[-1] -like "--target-org*"){ - $search = $CurrentLine[-1].Substring('--target-org '.Length) - $orgs.Keys | Where-Object { $_ -like "$search*" } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "--target-org $_", $_, 'ParameterValue', 'Custom completion description' - } + if(containsTargetOrgFlag @($CurrentLine[-1]) $targetOrgFlags){ + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags + + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + } }else{ $NextArg._command.flags.GetEnumerator() | Sort-Object -Property key | Where-Object { @@ -793,10 +896,11 @@ $scriptblock = { # This could be a coTopic. We remove the "_command" hashtable # from $NextArg and check if there's a command under the current partial ID. $NextArg.remove("_command") + $targetOrgFlag=getTargetOrgFlag $CurrentLine $targetOrgFlags - if($CurrentLine[-2] -like "--target-org*" -Or $CurrentLine[-1] -like "--target-org*"){#echo 'here' - $orgs.Keys | Where-Object { $search = $CurrentLine[-1]; if ($search -eq "" -Or $search -eq "--target-org") { return $true } else { return $_ -like "*$search*" } } | Sort-Object | ForEach-Object { - New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' + if(containsTargetOrgFlag @($CurrentLine[-1], $CurrentLine[-2]) $targetOrgFlags){ + orgs | Where-Object { $search = $CurrentLine[-1].replace($targetOrgFlag, "").Trim(); return ($search -eq "" ? $true : $_ -like "*$search*") } | Sort-Object | ForEach-Object { + New-Object -Type CompletionResult -ArgumentList "$_", $_, 'ParameterValue', 'Custom completion description' } }else{ if ($NextArg.keys -gt 0) { From bef108829fba89320a5f92150cd6c8a493cc133d Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:20:08 +0100 Subject: [PATCH 23/25] test: zsh tests fixed --- src/autocomplete/zsh.ts | 7 ++----- test/autocomplete/zsh.test.ts | 31 ++++++------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/autocomplete/zsh.ts b/src/autocomplete/zsh.ts index 1930c6c7..49af3893 100644 --- a/src/autocomplete/zsh.ts +++ b/src/autocomplete/zsh.ts @@ -89,11 +89,8 @@ export default class ZshCompWithSpaces { ${this.config.binAliases?.map((a) => `compdef ${a}=${this.config.bin}`).join('\n') ?? ''} _orgs(){ - if [[ -z $ORGS ]]; then - export ORGS=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) - fi - - _describe -t ORGS 'ORGS' ORGS && return 0 + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + _describe -t orgs 'orgs' orgs && return 0 } ${this.topics.map((t) => this.genZshTopicCompFun(t.name)).join('\n')} diff --git a/test/autocomplete/zsh.test.ts b/test/autocomplete/zsh.test.ts index 22a34ca1..7dac6b98 100644 --- a/test/autocomplete/zsh.test.ts +++ b/test/autocomplete/zsh.test.ts @@ -6,7 +6,6 @@ import {fileURLToPath} from 'node:url' import ZshCompWithSpaces from '../../src/autocomplete/zsh.js' // autocomplete will throw error on windows ci -import {testOrgs} from '../helpers/orgruntest.js' import {default as skipWindows} from '../helpers/runtest.js' class MyCommandClass implements Command.Cached { @@ -209,18 +208,12 @@ skipWindows('zsh comp', () => { it('generates a valid completion file.', () => { config.bin = 'test-cli' - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli _orgs(){ - local orgs - orgs=( - org1alias -org2.username@org.com -org3alias - ) - + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) _describe -t orgs 'orgs' orgs && return 0 } @@ -353,18 +346,12 @@ _test-cli it('generates a valid completion file with a bin alias.', () => { config.bin = 'test-cli' config.binAliases = ['testing'] - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli compdef testing=test-cli _orgs(){ - local orgs - orgs=( - org1alias -org2.username@org.com -org3alias - ) - + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) _describe -t orgs 'orgs' orgs && return 0 } @@ -497,19 +484,13 @@ _test-cli it('generates a valid completion file with multiple bin aliases.', () => { config.bin = 'test-cli' config.binAliases = ['testing', 'testing2'] - const zshCompWithSpaces = new ZshCompWithSpaces(config as Config, testOrgs) + const zshCompWithSpaces = new ZshCompWithSpaces(config as Config) expect(zshCompWithSpaces.generate()).to.equal(`#compdef test-cli compdef testing=test-cli compdef testing2=test-cli _orgs(){ - local orgs - orgs=( - org1alias -org2.username@org.com -org3alias - ) - + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) _describe -t orgs 'orgs' orgs && return 0 } From b2fd8b0b323b3c1c1e60c4bbc0f9d4842e2b4894 Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:23:30 +0100 Subject: [PATCH 24/25] test: fix indentation for installations instructions --- src/commands/autocomplete/index.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/commands/autocomplete/index.ts b/src/commands/autocomplete/index.ts index 247a22cd..35af1924 100644 --- a/src/commands/autocomplete/index.ts +++ b/src/commands/autocomplete/index.ts @@ -105,20 +105,20 @@ export default class Index extends AutocompleteBase { const note = noteFromShell(shell) this.log(` - ${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)} +${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)} - 1) Add the autocomplete ${shell === 'powershell' ? 'file' : 'env var'} to your ${shell} profile and source it +1) Add the autocomplete ${shell === 'powershell' ? 'file' : 'env var'} to your ${shell} profile and source it - ${chalk.cyan(instructions)} +${chalk.cyan(instructions)} - ${chalk.bold('NOTE')}: ${note} +${chalk.bold('NOTE')}: ${note} - 2) Test it out, e.g.: - ${chalk.cyan(`$ ${bin} ${tabStr}`)} # Command completion - ${chalk.cyan(`$ ${bin} command --${tabStr}`)} # Flag completion +2) Test it out, e.g.: +${chalk.cyan(`$ ${bin} ${tabStr}`)} # Command completion +${chalk.cyan(`$ ${bin} command --${tabStr}`)} # Flag completion - Enjoy! - `) +Enjoy! +`) } } } From f9d7e2ecb3b63bab7966c0f7bb15686a9dd18aed Mon Sep 17 00:00:00 2001 From: Gil Gourevitch <2204051+gilgourevitch@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:28:52 +0100 Subject: [PATCH 25/25] test: private methods tests fixed --- test/commands/autocomplete/create.test.ts | 31 ++++++++--------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/test/commands/autocomplete/create.test.ts b/test/commands/autocomplete/create.test.ts index 1db61675..8046c383 100644 --- a/test/commands/autocomplete/create.test.ts +++ b/test/commands/autocomplete/create.test.ts @@ -10,7 +10,6 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../. const config = new Config({root}) // autocomplete will throw error on windows ci -import {testOrgs} from '../../helpers/orgruntest.js' import {default as skipWindows} from '../../helpers/runtest.js' skipWindows('Create', () => { @@ -20,7 +19,7 @@ skipWindows('Create', () => { let plugin: any before(async () => { await config.load() - cmd = new Create([], config, testOrgs) + cmd = new Create([], config) plugin = new Plugin({root}) cmd.config.plugins = [plugin] plugin._manifest = () => @@ -78,12 +77,6 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " -local orgs=" -org1alias -org2.username@org.com -org3alias -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -97,6 +90,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) @@ -143,7 +138,7 @@ complete -o default -F _oclif-example_autocomplete oclif-example\n`) await spacedConfig.load() spacedConfig.topicSeparator = ' ' // : any is required for the next two lines otherwise ts will complain about _manifest and bashCompletionFunction being private down below - const spacedCmd: any = new Create([], spacedConfig, testOrgs) + const spacedCmd: any = new Create([], spacedConfig) const spacedPlugin: any = new Plugin({root}) spacedCmd.config.plugins = [spacedPlugin] spacedPlugin._manifest = () => @@ -168,12 +163,6 @@ autocomplete:foo --bar --baz --dangerous --brackets --double-quotes --multi-line foo --bar --baz --dangerous --brackets --double-quotes --multi-line --json " -local orgs=" -org1alias -org2.username@org.com -org3alias -" - local targetOrgFlags=("--target-org" "-o") function _isTargetOrgFlag(){ @@ -187,6 +176,8 @@ function _isTargetOrgFlag(){ } function _suggestOrgs(){ + local orgs="$(sf autocomplete --display-orgs bash 2>/dev/null)" + if [[ "$cur" != "-"* ]]; then opts=$(printf "%s " "\${orgs[@]}" | grep -i "\${cur}") COMPREPLY=($(compgen -W "$opts")) @@ -275,12 +266,12 @@ _oclif-example () { local _command_id=\${words[2]} local _cur=\${words[CURRENT]} local -a _command_flags=() - local -a _orgs=( -org1alias -org2.username@org.com -org3alias -) + _orgs(){ + local orgs=(\${(@f)$(sf autocomplete --display-orgs zsh 2>/dev/null)}) + echo "$orgs" + } + ## public cli commands & flags local -a _all_commands=( "autocomplete:display autocomplete instructions"