CLI with nodejs - node.js

I'm developing a CLI in node that will be published to NPM.
Since it's a CLI application, I want it to be included in the path once it's installed, so it's not require to type "node my-app.js" to run it. I want it to run with only "my-app".
In the package.json, I'm including:
"bin": {
"my-all" : "./my-app.js"
},
But this makes fail the installation via NPM with this error
Error: ENOENT, chmod '/home/user1/node_modules/my-app/my-app'

Assuming you're on some kind of unix (linux, osx), put this line at the top of your script:
#!/usr/bin/env node
Also make sure you set the file to executable (chmod a+x my-all).
That should take care of the need to type node my-app.js, and enable you instead to just type ./my-app.js.
As for the npm packaging stuff I am not sure why it fails, but I'm guessing it's an issue with the path or location of your my-app.js .
If an executable script is put anywhere in the PATH, then it will be run just like anything else. If you run which npm, you will see where the npm executable script is located. On my system, most node executable (or executable npm scripts) goes into /usr/local/bin. I'm assuming your package.json can be set to put it somewhere in the path. If you need to change the path, then modify your .profile, or alternatively your system path.

Related

Using NPX command for shell script shebang / interpreter

I'd like to run a command line script using the coffee executable, but I'd like to call that executable through npx.
Something like #!/usr/bin/env npx coffee does not work, because only one argument is supported via env.
So, is there a way to run an npx executable via env?
Here is a solution using ts-node.
Any single OS, ts-node installed globally
#!/usr/bin/env ts-node
// TypeScript code
You probably also need to install #swc/core and #swc/cli globally unless you do further configuration or tweaking (see the notes at the end). If you have any issues with those, be sure to install the latest versions.
macOS, ts-node not installed globally
#!/usr/bin/env npx ts-node
// TypeScript code
Whether this always works in macOS is unknown. There could be some magic with node installing a shell command shim (thanks to #DaMaxContext for commenting about this).
This doesn't work in Linux because Linux distros treat all the characters after env as the command, instead of considering spaces as delimiting separate arguments. Or it doesn't work in Linux if the node command shim isn't present (not confirmed that's how it works, but in any case, in my testing, it doesn't work in Linux Docker containers).
This means that npx ts-node will be treated as a single executable name that has a space in it, which obviously won't work, as that's not an executable.
See the notes at the bottom about npx slowness.
Cross-platform with ts-node not installed globally in macOS, and some setup in linux
Creating a shebang that will work in both macOS and Linux (or macOS using Docker running a Linux image), without having to globally install ts-node and other dependencies in macOS, can be accomplished if one is willing to do a little bit of setup on the Linux/Docker side. Obviously, Linux must have node installed.
Use the #!/usr/bin/env npx ts-node shebang. We just have to fool Linux into thinking that npx ts-node with the space is actually a valid executable name.
Build a named Docker image that has the required dependencies globally installed and a symbolic link making npx ts-node resolve to just ts-node.
Here is an example all-in-one command line on macOS that will both build this image and run it:
docker buildx build -t node-ts - << EOF
FROM node:16-alpine
RUN \
npm install -g #swc/cli #swc/core ts-node \
&& ln -s /usr/local/bin/ts-node '/usr/local/bin/npx ts-node'
ENV SWC_BINARY_PATH=/usr/local/lib/node_modules/#swc/core/binding
WORKDIR /app
EOF
docker run -it --rm \
-v "$(pwd):/app" \
node-ts \
sh
Note that for this example script to work, the above line containing EOF must not have any other characters on the line, before or after it, including spaces.
Inside of the running container, all .ts scripts that have been made executable chmod +x script.ts will be executable simply by running them from the command line, e.g., ./test-script.ts. You can replace the above sh with the name of the script, as well (but be sure to precede it with ./ so Docker knows to run it as an executable instead of pass it as an argument to node).
Additional Thoughts & Considerations
There are other ways to achieve the desired functionality.
The docker run command can mount files into the image, including mounting executables in various directories. Some creative use of this could avoid needing to install anything or build a docker image first.
The install commands could be part of the docker run instead of pre-building an image, but then would be performed on each execution, taking much longer.
The PATH could be modified in macOS, linux, and in the docker build to add the folder containing ts-node's bin.js from any ts-node dist directory, then a shebang of #!/usr/bin/env bin.js should theoretically work (and can try bin-esm.js to avoid needing SWC, though this enters experimental node territory and may not be suitable for production scripts). This works in macOS, and in Docker outside of an npm project, and in Docker inside of an npm project configured to use TS & swc by passing the --skipProject flag to ts-node or setting environment variable TS_NODE_SKIP_PROJECT=true. A working test command line example: docker run -it --rm -v "$(pwd):/app" -e TS_NODE_SKIP_PROJECT=true -w /app --entrypoint sh node:16-alpine -c 'PATH="$PATH:/app/node_modules/ts-node/dist" ./test.ts'.
Any named executable that can be found in the PATH and run via direct command can be a shebang (using #!/usr/bin/env executable). It can be a shell script, a binary file, anything. A shell script can easily be put at a known location, added to the PATH, and then call whatever you like. It could be multi-statement, compiling the file to .js, then running that. Whatever your needs are.
In some special cases you might want to simply use node as your shebang executable, setting node options through environment variables to force ts-node as your loader. See ts-node Recipes:Other for more info on this.
Notes:
The SWC_BINARY_PATH environment variable ensures that ts-node can find the architecture-specific swc compiler (to avoid error "Bindings not found"). If you're running on only one architecture, you won't need it. Or, if you are mounting node_modules that have these #swc packages already installed for the correct architecture, you won't need it.
It is possible to install node_modules binaries for multiple architectures. The way to do this varies between the different package managers. For example, yarn 3 lets you define which binaries to install all at once in .yarnrc.yml. There are other options for npm and possible yarn 1 (and 2?) using environment variables.
ts-node does offer options for running without swc (though this is slower). You could try shebangs with ts-node-esm instead of ts-node. Look at all the symlinks in the /usr/local/bin folder or consult ts-node documentation for more information. If ts-node
It is possible to run .ts files directly using node and setting node options in environment variables. node --loader=ts-node does work, in recent versions (16+?). The experimental mode warnings can be suppressed.
There are some crazy ways to trick the shell to run JavaScript instead of a unix shell. Check out this answer that uses a normal sh shebang, but a clever shell statement to transfer execution over to node, that is basically ignored by JavaScript. This isn't great as it requires extra lines of trickery, but could help some people. Other answers on the page are also instructive and it's worth reviewing to get the full picture.
Some of the complexity here might go away if running .ts files outside of an npm project. In my own testing in Docker, the context was always in a project having its own tsconfig.json and swc installed, so with a different setup you might have different results. It proved to be difficult to get ts-node to ignore npm project context found with the executed .ts file.
The difference between ESM and CommonJS module handling has not been explained here. This is a complicated topic and beyond the scope of this answer.
Suffice it to say that if you can figure out how to run your scripts from the command line in the form executable [options] [file], then you should be able to figure out how to run ./[file] with an appropriate shebang, by mixing and matching all the ideas presented here. You don't have to use ts-node. You can directly use node, swc, tsc itself (by first compiling and then running any .js file or set of .js files in the found context), or any utility or tool that is able to compile or run TypeScript.
Note that using npx is significantly slower than running ts-node directly, because it may need to download the ts-node package, and dependencies, every time it runs.
Some various random tips on possible strategies for SWC architecture support:
https://socket.dev/npm/package/#rnw-community/nestjs-webpack-swc
https://github.com/yarnpkg/yarn/issues/2221

how to run mbpipe... a program I just installed

I'm trying to use this tool: https://github.com/mapbox/node-mbtiles/wiki/Post-processing-MBTiles-with-MBPipe
I've installed mbtiles with npm install -g mbtiles. I've also installed it locally (in the dir I'm working in) with just plain npm install mbtiles.
That part worked (I was able to download the files), but now according to the readme I can just start entering in commands like
mbpipe 'pngquant 64' myMbTilesFile.mbtiles and it's supposed to work?
Umm... don't I have to run a specific script file (like "node scriptfile.js")? This is acting like I can call functions within a script and pass it variables? I can tell you, as the user mentioned, that 'mbpipe' is within the utils.js file I have.... but how am I supposed to use it?
when I enter in the above command, I of course get "mbpipe: command not found"
So... what are they talking about?

node_modules/.bin/package.cmd file in node js

I am new to node js. I am trying to build a npm module and confused a bit with cmd file present in /node_modules/.bin folder with the name of the package locally.
I installed multiple packages as dependencies and found that cmd files are different.
jade.cmd
#IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\jade\bin\jade" %*
) ELSE (
#SETLOCAL
#SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\jade\bin\jade" %*
)
mocha-casperjs.cmd
#IF EXIST "%~dp0\/bin/sh.exe" (
"%~dp0\/bin/sh.exe" "%~dp0\..\mocha-casperjs\bin\mocha-casperjs" %*
) ELSE (
#SETLOCAL
#SET PATHEXT=%PATHEXT:;.JS;=;%
/bin/sh "%~dp0\..\mocha-casperjs\bin\mocha-casperjs" %*
)
My question is, if it is auto-generated by NPM, why npm creates 2 different files for 2 different packages. Is is something a user creates and tells NPM?
tl;dr
On Windows, npm creates the wrapper batch file (*.cmd) based on whatever shell/interpreter is specified in the script file's shebang line.
What you're seeing is npm's emulation of the Unix shebang mechanism for Windows, where the very 1st line of an executable plain-text file (script), if it starts with magic characters #!, tells the OS what interpreter/shell to pass the script to for execution (see this answer of mine for more information).
Since Windows doesn't support shebang lines, npm creates a wrapper *.cmd (batch) file, that explicitly invokes the "binary" file (the script file specified in the bin key of package.json) with whatever executable is specified in the script file's shebang line.
In other words: npm parses the script's shebang line to determine what shell/interpreter to invoke, and creates the wrapper batch script accordingly.
If we peek at ./bin/jade.js inside the jade package, we see #!/usr/bin/env node as the shebang line, which is why the jade.cmd wrapper file invokes node.exe.
This is the typical case: a script written in JavaScript that must be executed by the Node.js engine.
However, any other shell/interpreter may be specified too, but doing so only makes sense if a given shell/interpreter is also available on Windows; for node, that is a given, but, as the contents of mocha-casper.cmd shows, it makes no sense with the Unix default shell, /bin/sh (the ./bin/mocha-casperjs file from package mocha-casper.js contains shebang line #!/bin/sh).
The makers of the mocha-casperjs have chosen to roll their own Windows integration by re-implementing the Unix shell script for cmd.exe as mocha-casperjs.bat (also a batch file) - however, since npm is unaware of this, this batch file is not placed in the PATH (global) / not discoverable by name only (when calling CLIs in a project context).
More generally, for scripts to also work on Windows, it doesn't make sense to use shebang lines with absolute, POSIX-style paths - EXCEPT if the target shell/interpreter is specified by indirect invocation via /usr/bin/env, which signals to npm that it should be looked for in the PATH (again, see this answer of mine for more information).
(Additionally, the wrapper batch files also look in their own directory for the executable, but that is rarely helpful, given that npm wouldn't copy them there even if you explicitly added them as an entry in your package.json's bin key.)
As an aside: recent versions of npm also create extension-less wrapper scripts for Unix environments, notably Cygwin, and Bash on Ubuntu on Windows.
When creating your own packages, for CLIs ("binaries") - directly executable scripts that act like command-line utilities - that are part of an npm package to work on Windows too, you must:
Add a shebang line to your scripts - even if you only ever intend to run them on Windows.
Define that shebang line as #!/usr/bin/env <interpreter-executable-filename-without-extension>; typically - if your script is written in JavaScript and must be executed with node - use:
#!/usr/bin/env node
In your package.json file's bin key, define the script's key without extension., because .cmd is directly appended to the key name when the wrapper batch file is created (on Unix, a symlink is created named for the key as-is); e.g.:
"bin": { "foo": "./bin/fooj.js" }
Caveat: If the script pointed to by the bin key in package.json has extension .js but has no shebang line, the wrapper script will invoke the .js file directly, which will by default execute it with WSH (specifically, with JScript, its JavaScript engine) rather than Node.js, which won't work as intended - see this question for what symptoms you would see.
You can tell npm to make executable file provided with your package accessible from ./node_modules/.bin directory (in case of local install) or globally (when module is installed globally). You should place bin field into package.json and specify relative path to script. For example, jade package.json contains the following code:
"bin": {
"jade": "./bin/jade.js"
}
When installing jade package, npm makes this script (./bin/jade.js) accessible by generating, if neccessary, wrapper script (jade.cmd) which contents depend on current OS, shell, and the type of script you wish to make accessble. Jade uses .js script, and npm generates jade.cmd for your OS that will launch node and pass script name as argument. But mocha-casperjs uses shell scripts, so contents of generated mocha-casperjs.cmd are different — it starts sh.exe instead of node.
You can read about bin field of package.json here: https://docs.npmjs.com/files/package.json#bin

Node and Grunt display node identifier message instead of executing

I have installed node on my machine and have two executables, node.exe and nodejs.exe in my directory. Through the NPM I have installed grunt and jshint for grunt; following this guide:
http://strongloop.com/strongblog/use-grunt-js-and-the-power-of-javascript-to-automating-repetitive-tasks/
When I try and run "grunt" "node" or "grunt jshint" into the windows command prompt or Git Bash then I recieve the following response:
The node identifier for <machine number> is <id number>
I have in my path the location to the \nodejs\node.exe file
I do not have admin or root access to my machine, but can put requests in to add permissions or extra items to the Path. However, I was able to get JSHint to work in Sublime Text by specifying the node path as "nodejs/nodejs.exe"
Would changing my path to nodejs.exe instead fix this; or is there a deeper issue into the setup I have configured that would be causing this problem?
check your system %PATH% variable (echo %PATH%). Most certainly there is another application called node.bat, node.cmd or node.exe that is in your PATH before nodejs and thats why the system finds this first. I'm 99% sure that this is not the output from nodejs
if you have access to your path try to prepend the nodejs path

I can't run test with "vows test/*" command on windows. How to use it? node.js

I've installed vows as module of my project and I've added the path "node_modules\vows\bin" to my environment path variable of windows vista.
note: I've also renamed "node_modules\vows\bin\vows" to vows.exe, because without the extension I get this error: 'vows' is not recognized as an internal or external command,
operable program or batch file.
Now wherever I put "vows" in my cmd in windows nothing happens, I cd into my test folder and I run "vows myFirstTest.js" and nothing happens. (when I say nothing happens my cursor in cmd is going to the top and then return to it's original position and it's doing this forever, therefore each time I try a vows command in cmd I have to close the cmd to run another command).
What I'm doing bad?
thanks
NPM is great at globally installing packages and making the executable for each operating system (UNIX-ish + Windows), so proceed with the following:
npm install -g vows

Resources