Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

My objective is to write a CLI in Typescript/node.js, that uses --experimental-specifier-resolution=node, written in yargs with support for autocompletion.

To make this work, I use this entry.sh file, thanks to this helpful SO anwswer (and the bin: {eddy: "./entry.sh"} options in package.json points to this file)

#!/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 "$@"

This works great, and I can use the CLI. However, autocompletion does not work. According to yargs I should be able to get autocompletion by outputting the result from ./entry.sh completion to the ~/.bashrc profile. However this does not seem to work.

Output from ./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-###

I tried modifying the completion output, but I don't really understand bash - just yet 😅

Update

Working on a reproducible example (WIP). Repo is here.

Currently one of the big differences is that npm link does not work the same in the 2 different environments. It's only in the repo where I'm trying to reproduce that /usr/local/share/npm-global/bin/ is actually updated. Currently trying to investigate this.

like image 346
DauleDK Avatar asked Jun 11 '26 19:06

DauleDK


1 Answers

You can try specifying the scriptName in your entry.js file to the name of your wrapper script. This may force generation of completion name using it. I haven't tried it but looking at the source code of yargs, it looks like the $0 parameter can be altered using scriptName, which in turn will affect how the completion-generation function generate the completion code:

In yargs-factor.ts:

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

In 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);
  }

Also I'm not sure how the "bin" configuration works but maybe because of scriptName you'd no longer need a wrapper.

Make sure the version of yargs you use supports this.

Also as a side note I thought about suggesting to modify the generated completion script directly but besides being hackish that might also still lead to the script name being unrecognized during completion. Anyhow I just looked at the right approach first.

The modified version would like this:

_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

Another note: If the script name needs to be dynamic based on the name of its caller, you can make it identifiable through an environment variable, so in entry.sh you can declare it like this:

export ENTRY_JS_SCRIPT_NAME=entry.sh
node ...

Then somewhere in entry.js, you can access the variable name through this:

process.env.ENTRY_JS_SCRIPT_NAME

Maybe even just specify $0 or ${0##*/} whatever works:

export ENTRY_JS_SCRIPT_NAME=$0
like image 96
konsolebox Avatar answered Jun 14 '26 08:06

konsolebox



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!