14

Our team has built a small CLI used for maintenance. The package.json specifies a path for with the bin property, and everything works great; "bin": { "eddy": "./dist/src/cli/entry.js"}

Autocompletion is achived by using yargs@17.0.1. However we recently converted the project to use es6 modules, because of a migration to Sveltekit, i.e. the package.json now contains type: module. Because of this, the CLI now only works if we run with:

what works

node --experimental-specifier-resolution=node ./dist/src/cli/entry.js help

However, if we run this without the flag, we get an error "module not found":

Error [ERR_MODULE_NOT_FOUND]: Cannot find module...

So the question is

Can we somehow "always" add the experimental-specifier-resolution=node to the CLI - so we can continue to use the shorthand eddy, and utilize auto completion?

DauleDK
  • 3,313
  • 11
  • 55
  • 98
  • If your CLI is written in TypeScript, I can share the solution. And can you help with a few details? Are you able to run the CLI with `npm start`? – Vinay Sharma Jun 27 '21 at 18:39
  • Hi @VinaySharma - yes the CLI is written in Typescript, part of a mono-repo. We don't run the CLi with npm start, but with `node --experimental-specifier-resolution=node ./dist/src/cli/entry.js help` – DauleDK Jun 27 '21 at 19:47

2 Answers2

12

There are two probable solutions here.

Solution 1

Your entry.js file should start with a shebang like #!/usr/bin/env node. You cannot specify the flag directly here, however, if you could provide the absolute path to node directly in the shebang, you can specify the flag.

Assuming you have node installed in /usr/bin/node, you can write the shebang in entry.js like:

#!/usr/bin/node --experimental-specifier-resolution=node

(Use which node to find the absolute path)

However, this is not a very portable solution. You cannot always assume everyone has node installed in the same path. Also some may use nvm to manage versions and can have multiple version in different path. This is the reason why we use /usr/bin/env to find the required node installation in the first place. This leads to the second solution.

Solution 2

You can create a shell script that would intern call the cli entry point with the required flags. This shell script can be specified in the package.json bin section.

The shell script (entry.sh) should look like:

#!/usr/bin/env bash
/usr/bin/env node --experimental-specifier-resolution=node ./entry.js "$@"

Then, in your package.json, replace bin with:

"bin": { "eddy": "./dist/src/cli/entry.sh"}

So when you run eddy, it will run the entry.js using node with the required flag. The "$@" in the command will be replaced by any arguments that you pass to eddy.

So eddy help will translate to /usr/bin/env node --experimental-specifier-resolution=node ./entry.js help

Vivek
  • 2,665
  • 1
  • 18
  • 20
  • How do I include the `entry.sh` file, so it's part of the `tsc` build output? (currently it's not included) – DauleDK Jun 27 '21 at 20:01
  • You can use gulp or grunt to automate copying the file to dist. (The same way you handle static assets like images) – Vivek Jun 27 '21 at 20:12
  • Ok - so I just copied the file over manually, rebuilt everything, but still get module not found.. – DauleDK Jun 28 '21 at 06:52
  • Are you sure running this command directly still works: `/usr/bin/env node --experimental-specifier-resolution=node ./entry.js help` . If it works, can you try `./entry.sh help` – Vivek Jun 28 '21 at 10:32
  • wait I actualy never tried the first solution! The first solution seems to work fine ✅ – DauleDK Jul 02 '21 at 06:27
  • 2
    If you are trying to set up a typescript project, this is a nightmare. Thank you for the help – Daniel Kaplan Aug 23 '21 at 03:20
  • 2
    @Vivek Are you sure you can not pass flags as part of the hashbang? It seems to work for me. I have used this successfully to execute Node with ESM and TS support [here](https://github.com/0x80/firestore-facade/blob/main/packages/cli/src/cli.ts). I have only tested this on MacOS... Solution 2 I could not get working because ./entry.js is resolved from the CWD and not the file location of the bash script. In my case, that was problematic because like the OP I am executing this as an NPM package bin script. The CWD would then be the project root where the command is being executed. – Thijs Koerselman Dec 08 '21 at 11:21
  • @ThijsKoerselman How shebangs treat arguments are specific to operating systems and doesn't seem to be standardised. It might work on MacOS (I haven't tried), but I can confirm this doesn't work on recent Ubuntu releases. This article might be helpful: https://lists.gnu.org/archive/html/bug-sh-utils/2002-04/msg00020.html – Vivek Dec 08 '21 at 15:23
  • Hi @Vivek - I ended up using solution 2 as it is more robust. However I'm having issues with auto-completion. Created a question here, https://stackoverflow.com/q/70496353/3694288. I hope you have an idea about what I can do to make it work – DauleDK Dec 27 '21 at 13:52
  • solution #1, node `v18.7.0` getting error `Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.` – Filip Seman Aug 16 '22 at 11:37
-2

Just add a script to your package.json:
Assuming index.js is your entry point and package.json is in the same directory

{
  "scripts": {
    "start": "node --experimental-specifier-resolution=node index.js"
  }
}

Then you can just run on your console:

npm start