【问题标题】:How to get yargs auto-complete working, when using --experimental-specifier-resolution=node使用 --experimental-specifier-resolution=node 时如何让 yargs 自动完成工作
【发布时间】:2022-01-26 11:16:48
【问题描述】:

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

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

#!/usr/bin/env bash

full_path=$(realpath $0)
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: https://stackoverflow.com/questions/68111434/how-to-run-node-js-cli-with-experimental-specifier-resolution-node

/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 在两种不同的环境中的工作方式不同。仅在我试图重现 /usr/local/share/npm-global/bin/ 实际更新的存储库中。目前正在尝试对此进行调查。

【问题讨论】:

  • 修改启动文件后是否重启了 Bash?
  • 顺便说一句,您通常应该在所有包含文件名的变量周围使用双引号。见When to wrap quotes around a shell variable
  • 嗨@tripleee - 谢谢你给我这个提示。你在这里想什么变量? type_list?
  • $0$full_path 均未引用。只要它们包含琐碎的文件名,这将起作用,但如果您的路径中有空格等,则会爆炸。
  • 能否尝试在补全函数中提供entry.js的完整路径而不是`./dist/src/cli/entry.jsˋ?

标签: node.js bash yargs


【解决方案1】:

您可以尝试将entry.js 文件中的scriptName 指定为包装脚本的名称。这可能会强制使用它生成完成名称。没试过,看yargs的源码,好像$0参数可以用scriptName修改,反过来会影响completion-generation函数如何生成completion code:

yargs-factor.ts:

  scriptName(scriptName: string): YargsInstance {
    this.customScriptName = true;
    this.$0 = scriptName;
    return this;
  }

completion.ts:

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

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

    script = script.replace(/{{app_name}}/g, name);
    script = script.replace(/{{completion_command}}/g, cmd);
    return script.replace(/{{app_path}}/g, $0);
  }

我也不确定"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

甚至可以指定$0${0##*/} 任何可行的方法:

export ENTRY_JS_SCRIPT_NAME=$0

【讨论】:

  • 感谢您的精彩回答 - 尝试一切。到目前为止没有运气 - 但仍在尝试:)
  • 您需要更新您迄今为止尝试过的问题。它需要正确的组合才能正确完成。
  • 至少发布您现在正在使用的生成完成脚本并确保它已加载。
  • 我正在创建一个可重现的小型环境 - 等等 :)
  • 已更新问题,将继续发布调查结果。
【解决方案2】:

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

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

test-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()

test-cli.sh

#!/usr/bin/env bash

full_path="$(realpath "$0")"
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 &gt;&gt; ~/.bashrc
  3. source ~/.bashrc
  4. 利润??

【讨论】:

  • 我还建议将$node_path$script_path 放在双引号中,以避免意外的分词或通配符。使用node_path="$(which node)" 的必要性很奇怪。也许 codespaces 有一个导出的节点功能,您也可以通过添加 unset -f node 或简单地通过运行带有 exec 的命令来绕过它。所以它变成了exec node --experimental-specifier-resolution=node "$script_path" "$@"。要检查是否有函数,请使用type node
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-10
  • 2010-09-23
  • 2022-11-10
  • 1970-01-01
  • 2015-01-09
  • 2019-06-20
  • 2022-12-28
相关资源
最近更新 更多