0

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

Lu4
  • 14,873
  • 15
  • 79
  • 132

1 Answers1

2

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
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thank you @slebetman for your answer. I've took some time to check the things you suggest in your answer, and the fact is that actually it doesn't work. Using `ts-node` as shebang string still won't start `ts-node`, it's like `ts-node` was hardcoded not to start as a globals package, very weird though... As soon as I copy this non-working package folder to any other location on disk it suddenly starts working. I don't see any code where I could make a mistake, again it seems as a `ts-node` issue... – Lu4 Aug 25 '19 at 01:33
  • Can you run ts-node on its own? Like, just type `ts-node` in the console – slebetman Aug 25 '19 at 12:34
  • Yes I do, it works fine up until I try to run the code from npm global packages folder. Reproduction steps: 1) install the package `> npm i -g hcitool-reader@1.2.1`. 2) I try to launch the package `> hcitool-reader` (it should throw an exception). 3) then to locate the folder where the global package is located execute `> echo $(dirname $(which hcitool-reader))/../lib/node_modules/hcitool-reader` it shows the path where the package is installed. If you copy this folder to any other place and try launch the package from that folder it'll run normally but not from where it is installed... – Lu4 Aug 25 '19 at 15:48
  • In last versions I've ended up building the package into JS before publishing in this way the package works, but as I stated above it does not work with `ts-node` as an execution environment – Lu4 Aug 25 '19 at 15:51