使用 --experimental-specifier-resolution=node 时如何让 yargs 自动完成工作

How to get yargs auto-complete working, when using --experimental-specifier-resolution=node

我的 objective 是用 Typescript/node.js 编写一个 CLI,它使用 --experimental-specifier-resolution=node,用 yargs 编写,支持自动完成。

为了完成这项工作,我使用了这个 entry.sh 文件,感谢这个有用的 (package.json 中的 bin: {eddy: "./entry.sh"} 选项指向这个文件)

#!/usr/bin/env bash

full_path=$(realpath [=10=])
dir_path=$(dirname $full_path)
script_path="$dir_path/dist/src/cli/entry.js"

# Path is made thanks to: https://code-maven.com/bash-shell-relative-path
# Combined with knowledge from: 

/usr/bin/env node --experimental-specifier-resolution=node $script_path "$@"

效果很好,我可以使用 CLI。但是,自动完成不起作用。根据 yargs,我应该能够通过将 ./entry.sh completion 的结果输出到 ~/.bashrc 配置文件来获得自动完成功能。然而,这似乎不起作用。

来自./entry.sh completion的输出:

###-begin-entry.js-completions-###
#
# yargs command completion script
#
# Installation: ./dist/src/cli/entry.js completion >> ~/.bashrc
#    or ./dist/src/cli/entry.js completion >> ~/.bash_profile on OSX.
#
_entry.js_yargs_completions()
{
    local cur_word args type_list

    cur_word="${COMP_WORDS[COMP_CWORD]}"
    args=("${COMP_WORDS[@]}")

    # ask yargs to generate completions.
    type_list=$(./dist/src/cli/entry.js --get-yargs-completions "${args[@]}")

    COMPREPLY=( $(compgen -W "${type_list}" -- ${cur_word}) )

    # if no match was found, fall back to filename completion
    if [ ${#COMPREPLY[@]} -eq 0 ]; then
      COMPREPLY=()
    fi

    return 0
}
complete -o default -F _entry.js_yargs_completions entry.js
###-end-entry.js-completions-###

我尝试修改 completion 输出,但我不太明白 bash - 只是

更新

正在研究可重现的示例 (WIP)。 回购是 here.

目前最大的区别之一是 npm link 在 2 个不同的环境中的工作方式不同。只有在我试图重现 /usr/local/share/npm-global/bin/ 的回购协议中才实际更新。目前正试图对此进行调查。

您可以尝试将 entry.js 文件中的 scriptName 指定为包装脚本的名称。这可能会强制使用它生成完成名称。我没有尝试过,但是查看 yargs 的源代码,看起来 [=17=] 参数可以使用 scriptName 进行更改,这反过来会影响完成生成函数如何生成完成代码:

yargs-factor.ts中:

  scriptName(scriptName: string): YargsInstance {
    this.customScriptName = true;
    this.[=10=] = scriptName;
    return this;
  }

completion.ts中:

  generateCompletionScript([=11=]: string, cmd: string): string {
    let script = this.zshShell
      ? templates.completionZshTemplate
      : templates.completionShTemplate;
    const name = this.shim.path.basename([=11=]);

    // add ./ to applications not yet installed as bin.
    if ([=11=].match(/\.js$/)) [=11=] = `./${[=11=]}`;

    script = script.replace(/{{app_name}}/g, name);
    script = script.replace(/{{completion_command}}/g, cmd);
    return script.replace(/{{app_path}}/g, [=11=]);
  }

我也不确定 "bin" 配置如何工作,但可能是因为 scriptName 你不再需要包装器。

确保您使用的 yargs 版本支持此功能。

此外,作为旁注,我考虑过建议直接修改生成的完成脚本,但除了骇人听闻之外,还可能导致脚本名称在完成过程中无法识别。无论如何,我只是先看看正确的方法。

修改后的版本是这样的:

_entry.sh_yargs_completions()
{
    local cur_word args type_list

    cur_word="${COMP_WORDS[COMP_CWORD]}"
    args=("${COMP_WORDS[@]}")

    # ask yargs to generate completions.
    type_list=$(/path/to/entry.sh --get-yargs-completions "${args[@]}")

    COMPREPLY=( $(compgen -W "${type_list}" -- ${cur_word}) )

    # if no match was found, fall back to filename completion
    if [ ${#COMPREPLY[@]} -eq 0 ]; then
      COMPREPLY=()
    fi

    return 0
}
complete -o default -F _entry.sh_yargs_completions entry.sh

另请注意:如果脚本名称需要根据其调用者的名称动态变化,您可以通过环境变量使其可识别,因此在entry.sh中您可以这样声明:

export ENTRY_JS_SCRIPT_NAME=entry.sh
node ...

然后在entry.js的某个地方,你可以通过这个访问变量名:

process.env.ENTRY_JS_SCRIPT_NAME

甚至可以指定 [=17=]${0##*/} 任何有效的方法:

export ENTRY_JS_SCRIPT_NAME=[=15=]

谢谢大家。我最终得到的解决方案是 2 倍:

  1. 我在 yargs 配置中添加了 scriptName
  2. .sh 文件“wrapping”中,我使用 which node 大概设置了 --experimental-specifier-resolution=node 标志。

测试-cli.js

#!/usr/bin/env node

import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
import { someOtherModule } from './some-other-module';

someOtherModule();

yargs(hideBin(process.argv))
  .command('curl <url>', 'fetch the contents of the URL', () => {}, (argv) => {
    console.info(argv)
  })
  .command('curlAgain <url>', 'fetch the contents of the URL', () => {}, (argv) => {
    console.info(argv)
  })
  .demandCommand(1)
  .help()
  .completion()
  .scriptName('eddy') // <== Added thanks to konsolebox
  .parse()

测试-cli.sh

#!/usr/bin/env bash

full_path="$(realpath "[=11=]")"
dir_path="$(dirname $full_path)"
script_path="$dir_path/test-cli.js"

node_path="$(which node)" # <== Makes it work on github codespaces 

$node_path --experimental-specifier-resolution=node $script_path "$@"

package.json

{
  "name": "lets-reproduce",
  "type": "module",
  "dependencies": {
    "yargs": "^17.3.1"
  },
  "bin": {
    "eddy": "./test-cli.sh"
  }
}

安装自动补全的步骤:

  1. 运行 npm link
  2. 运行 eddy completion >> ~/.bashrc
  3. source ~/.bashrc
  4. 利润