Lu4
Lu4

Reputation: 15032

Why can't my globally installed npm package call ts-node?

Ok, I'm trying to create a CLI tool for my own use, basically it just parses standard output of hcitool (which reports on surrounding bluetooth devices).

The tool can be found here: https://github.com/lu4/hcitool-reader

The tool is expected to be run using ts-node (ts-node allows running TypeScript code on the fly).

My package works fine when installed globally from a local disk using following command:

bash> npm i -g /path/to/local/disk/hcitool-reader/repository

The tool can be validated (after it is installed) by executing:

bash> hcitool-reader

However if I remove old version and install the same code from NPM:

bash> npm uninstall -g hcitool-reader && npm i -g hcitool-reader

The package starts throwing node.js syntax exceptions pointing out that typescript syntax is wrong (which is a sign that ts-node wasn't registered properly).

bash> hcitool-reader

Trying to register ts-node with tsconfig.json found at:
/home/pi/.config/versions/node/v12.8.0/lib/node_modules/hcitool-reader/tsconfig.json
/home/pi/.config/versions/node/v12.8.0/lib/node_modules/hcitool-reader/src/index.ts:1
import 'reflect-metadata';
       ^^^^^^^^^^^^^^^^^^

SyntaxError: Unexpected string
    at Module._compile (internal/modules/cjs/loader.js:811:22)
    at Module._extensions..js (internal/modules/cjs/loader.js:879:10)
    at Object.require.extensions.<computed> [as .ts] (/home/pi/.config/versions/node/v12.8.0/lib/node_modules/hcitool-reader/node_modules/ts-node/src/index.ts:465:14)
    at Module.load (internal/modules/cjs/loader.js:731:32)
    at Function.Module._load (internal/modules/cjs/loader.js:644:12)
    at Module.require (internal/modules/cjs/loader.js:771:19)
    at require (internal/modules/cjs/helpers.js:68:18)
    at Object.<anonymous> (/home/pi/.config/versions/node/v12.8.0/lib/node_modules/hcitool-reader/bootstrap.js:15:20)
    at Module._compile (internal/modules/cjs/loader.js:868:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:879:10)

The weird thing about this error is that the code stops working only when it is located NPM's global package folder, in my case:

/home/pi/.config/versions/node/v12.8.0/lib/node_modules/hcitool-reader

in all other cases, from all other locations the code works fine.

Q: What's wrong with ts-node?

EDIT It seems to me that it is a ts-node issue I've created an issue in their repo, waiting for some comments from ts-node team

Upvotes: 0

Views: 2487

Answers (1)

slebetman
slebetman

Reputation: 113906

It's because you've written your bin/launch.js script to run with node, not ts-node. The first line of your script is:

#!/usr/bin/env node
//              ^^ launch with node.js

If you want to launch it with ts-node you should change it to:

#!/usr/bin/env ts-node

Shell sh-bang line

The first line of your script is what's commonly known as the sh-bang line (there are lots of different spellings for this). It is sh/csh compatible syntax that's been inherited by most common shell languages: bash/ksh/tcsh/tcl/node.

Strictly speaking it is not javascript syntax and should cause a syntax error but node.js specifically tolerates it if it's the first line of code. It causes the js file to be interpreted as a polyglot source (source code valid in more than one programming language).

The shell (bash/ksh/tcsh etc.) assumes that all scripts are written in the shell's own language (bash for bash, ksh for ksh etc.). The sh-bang syntax actually causes your file to be valid shell script source files. In all common unix shells the sh-bang command means:

Eval this string then treat the rest of this file as a comment then pass this file as the last argument to the string being eval'd.

So if your file's first line is:

#! /usr/bin/env wget https://stackoverflow.com/questions/57600624

The script will download this page.

The /usr/bin/env part is to run the env command which loads your current user environment then execute the rest of the line. The reason we run env is because the sh-bang syntax by default doesn't load your user environment which means you need to pass the absolute path of node or ts-node which may break if you run the script on different distros (ubuntu vs redhat) or if you install node or ts-node different ways (apt-get vs nvm). So calling env first makes sure your $PATH environment variable is set up correctly and in all Unix/Unix-like systems env is always installed in /usr/bin.

NPM is not node package manager!

NPM was not and is still not designed to specifically be a node package manager. Yes it has a lot of useful features supporting node.js (some like node_modules is even hardcoded in node.js) but it actually doesn't care what language your software is written in. It is a package manager for your OS, just like apt and yum (or brew for you Mac users). As such it does not have node specific support for running global executables - it just depends on what your OS/shell already supports. In this case it depends on the sh-bang line.

When you install a global script npm does not use the start command in package.json. It runs your script directly. This is because it's not a node-specific package manager so what it installs could be a Python script or a shell script or a binary executable written in assembly. This is why you need to make sure your "bin" script is normally executable by your OS.

Normally executable means that if you do this:

./bin/launch.js

then your OS can execute it just like all other programs on your system: node, apt-get, git etc. Not normal would be something like:

ts-node ./bin/launch.js

or:

java -jar ./minecraft.jar

Upvotes: 2

Related Questions