Why does binary npm file have two checks - node.js

Every binary inside node_modules/.bin have the following code:
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
# check if there is node executable in the same directory as this binary
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../angular-cli/bin/ng" "$#"
ret=$?
else
node "$basedir/../angular-cli/bin/ng" "$#"
ret=$?
fi
exit $ret
I sort of understand what this code is doing (comments are mine), but is there anywhere an explanation why they are here (some use cases)?

Checking for the node binary in the same directory is for the cases when this module was installed globally and has an executable file in the same place as Node. It makes sure that it uses the same node for which it was installed, even if you have some other Node in your PATH.
The else branch uses just node which means the same binary as you get by typing:
which node
which is the first node in one of the directories in the PATH environment variable.
The problem with installing Node scripts is that they have to have some shebang line. People usually use #!/usr/bin/env node to run whatever node you have in PATH. But it may be a different node (possibly incompatible) than the one that was actually used to install that script.
Incidentally, that is one of the reasons why I prefer installing Node from sources than from the binary packages, because otherwise my npm script can run the wrong node if I have multiple versions installed. See my tutorial on Node installation for details.
Thye Cygwin test checks if the output of the uname command contains the word CYGWIN and in that case uses a Cygwin-specific path resolution using cygpath -w "$basedir". Cygwin is a collection of GNU and Open Source tools which provide functionality similar to a Linux distribution on Windows - see: https://www.cygwin.com/ - and it does some path translation to make scripts written for Unix and Linux work on Windows. For Linux the output of uname is just Linux. On Cygwin it contains CYGWIN.

Related

A shell script to check the version of NodeJS doesn't work in Git Bash on Windows

I am trying to make a shell script to automatically download, compile and build some program I've made. You can see the full script on my blog. My program requires NodeJS 11 or newer, so I am trying to make my script output an appropriate error message in case it's not installed. Here is what I've tried:
node_version=$(node -v) # This does not seem to work in Git Bash on Windows.
# "node -v" outputs version in the format "v18.12.1"
node_version=${node_version:1} # Remove 'v' at the beginning
node_version=${node_version%\.*} # Remove trailing ".*".
node_version=${node_version%\.*} # Remove trailing ".*".
node_version=$(($node_version)) # Convert the NodeJS version number from a string to an integer.
if [ $node_version -lt 11 ]
then
echo "NodeJS version is lower than 11 (it is $node_version), you will probably run into trouble!"
fi
However, when I try to run it in Git Bash on Windows 10, with NodeJS 18 installed and in PATH, here is what I get:
stdout is not a tty
NodeJS version is lower than 11 (it is 0), you will probably run into trouble!
What is going on here?
The problem here is a mismatch in expectations of how a terminal should behave from a program with unix/posix expectations compared to how terminals behave on windows. This is a known problem, and there is a longer discussion of the issue on the nodejs repository (although this is not unique to just nodejs).
To compensate there is a tool winpty which provides the missing expectations (or alternatively node-pty), although you normally do not need to install explicitly, it should be included in the normal Git bash installation.
For this reason node is normally set up to be an alias for winpty node, although that causes problems for stout/stdin redirects, so a common practice is to then invoke node as node.exe (e.g. node.exe --help | wc -l) to sometimes avoid using winpty.
For your case you probably want to change your script to
if ...is-running-on-windows... # Check https://stackoverflow.com/q/38086185/23118
then
# I expect one of these to work
NODE_BINARY=node.exe
NODE_BINARY="winpty node"
else
NODE_BINARY=node
fi
node_version=$($NODE_BINARY -v)

Globally installed npm-package not running

I've just published a new package into npm, and its not running as expected.
Running globally "poker-odds-calc" will produce this error:
/c/Users/username/AppData/Roaming/npm/poker-odds-calc: line 1: /node_modules/poker-odds-calc/dist/lib/bin/poker-odds-calc.js: No such file or directory
AppData\Roaming\npm\poker-odds-calc
"$basedir/node_modules/poker-odds-calc/dist/lib/bin/poker-odds-calc.js" "$#"
exit $?
The above content is the reason for why the module doesnt run as global module, but I have no clue how to force npm to add the correct content to this file.
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/node_modules/poker-odds-calculator/dist/bin/poker-odds-calculator.js" "$#"
ret=$?
else
node "$basedir/node_modules/poker-odds-calculator/dist/bin/poker-odds-calculator.js" "$#"
ret=$?
fi
exit $ret
You are missing the shebang character sequence in your command-line script. Thus, NPM cannot install the binary file properly.
Solution
You need to add a shebang character sequence at the top of your typescript command-line source (/src/lib/bin/poker-odds-calc.ts). In other words, the top of the file needs to look like this:
#!/usr/bin/env node
import * as argv from "argv";
import Table from "../Table";
import {CardsFromString, Log} from "../Utils";
Upon package installation, NPM will look for the first line in each file defined in package.json's "bin" config and act accordingly in your operating system. In Windows, NPM creates different types of binary files depending on what you have defined at the top. If a node shebang is present, NPM creates a binary that attempts to execute the command-line script with node (similar to the content you wished for above). If shebang is omitted, NPM creates a proxy binary that outsources the responsibility to Windows (likely to fail, as you have seen).
Reference
https://medium.com/netscape/a-guide-to-create-a-nodejs-command-line-package-c2166ad0452e

shebang under linux does not split arguments

I have kotlin script (but it can be any Linux command with arguments) for example:
#!/usr/bin/env kotlinc -script
println("hello world")
When I run it in Ubuntu I get:
/usr/bin/env: ‘kotlinc -script’: No such file or directory
but when I run in command line:
/usr/bin/env kotlinc -script
It works. It is no problem with finding path because script:
#!/usr/bin/env kotlinc
println("hello world")
works
For some reason under Ubuntu "#!/usr/bin/env kotlinc -script" treats "kotlinc -script" as single argument. But only in shell script header.
I need explicitly to run my script "#!/usr/bin/env kotlinc -script" because I want it to run properly on other distributions end environments where "kotlin" is in $PATH.
Is there a bug in Ubuntu coreutils or sth? Is there a way to fix it?
On Linux, you can't pass more than one argument via the shebang line. All arguments will be passed as a single string to the executable:
#!/bin/foo -a -b -c
will pass one option "-a -b -c" to /bin/foo, plus the contents of the file. Like if you would call:
/bin/foo '-a -b -c' contents-of-file.txt
The behaviour should be the same on most unix derivates nowadays, but it can differ, I haven't tested them all :)
It's hard to find proper documentation for this, the best I could quickly find was this: https://www.in-ulm.de/~mascheck/various/shebang/#splitting
As a workaround you would normally create a shell wrapper:
#!/bin/bash
exec kotlin --arg1 --arg2 ... /path/to/kotlin-script
Check your coreutils version:
apt-cache policy coreutils
Starting with coreutils 8.30 you will be able to use:
#!/usr/bin/env -S command arg1 arg2 ...
You may want to upgrade your coreutils
For me the solution was to install kotlin, since I did not yet have installed it and just downloaded https://github.com/bernaferrari/GradleKotlinConverter
and thought it should work.
sudo snap install kotlin --classic

Unable to install and run my own npm module in Linux

I've created a npm module that I intend to publish, but not without testing that it works first. So I install the module I'm working with, npm install -g . and it works well on my Windows computer, but it won't run on my Linux (Debian) computer. Instead I get the following error:
15:52 $ transval
: No such file or directory
The only thing I've found so far when I compare the generated cmd and bash file on my windows computer is that whilest (when comparing to, say, 'gulp') the cmd-files are identical in structre the bash files are not. The second line, where the basedir is set differs. This the full output of the published bash file for my module:
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/node_modules/transval/bin/transval.bin.js" "$#"
ret=$?
else
node "$basedir/node_modules/transval/bin/transval.bin.js" "$#"
ret=$?
fi
exit $ret
But if I compare the top two lines with any other file there is a significant difference! This is the top two lines from any other module, such as gulp:
#!/bin/sh
basedir=`dirname "$0"`
All other bash files get that dirname. If I change my bash file to that basedir it all of a sudden works. It is driving me mad!
EDIT:
These two files are created when I run the command npm install -g . (thus installing my package globally for testing) or when I have published (i.e. npm publish), so I'm not generating these files my self.
My package.json has a bin entry which points at a file that looks like this:
#!/usr/bin/env node
var app = require('../bundle.js');
app.init(process.argv);
Anyone have any idea why it would work on Windows and not in Linux?
Ok, found the problem. It seems to have been a problem with publishing from Windows. Once I had published from Linux (Ubuntu in this case) I could install it on both Linux and Windows computers.
I'm not sure what the reason for this is, be it some npm bug or an issue with doze line breaks, but now it's working :)
I did try to publish previously from Linux, and failed, but with an old version of Node (4.something) and that didn't work but now I've upgraded to the latest version and it works well, so that might've had something to do with it.
Edit:
I can now verify that publishing on a Debian machine running node 6.2.2 creates an unusable published version whereas publishing on a Ubuntu machine running node 7.4.0 works well and can be installed and run anywhere. Both machines are running npm version 4.0.5.
Edit Per additional information in the OP's answer, it is indeed a line-ending problem. The problem actually is not related to $() vs. ``.
When generating on Windows, the lines end with a carriage return and a linefeed, \r\n. However, when running the generated script on Debian, only the \n is taken as the end of line. As a result, the assignment to basedir is effectively:
basedir=$(dirname "...")$'\r'
# ^^^^^ Carriage return! Oops!
I think that is why the error message was ': No such file or directory': before the :, the contents of $basedir were actually printed, ending with the \r. The \r moved the cursor back to the beginning of the line, then the rest of the error message, beginning with :, overprinted the path. (That's a guess, though — I can't reproduce the exact error message on my system.)
A workaround is to put a # (space-hash) after the basedir assignment:
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") #
# add these ^^
That way the \r will be part of a comment and not part of basedir.
Note: see this question and its answers for other ways of getting $basedir.
Per chat, the OP is going to add the $basedir values for both options tomorrow.
For reference, here's where we are at present:
Per this answer, npm generates the wrapper scripts based on bin entries in the package.json.
npm uses the cmd-shim module to make the scripts.
cmd-shim was updated 2013/10/28 to use the dirname ... echo ... sed sequence so that it would work on msysgit.
gulp and other scripts using dirname "$0" were presumably generated with a cmd-shim predating that update.
The OP's Debian /bin/sh is apparently dash (currently 0.5.7-4 in debian stable).
for the OP, on Debian, bash is version: 4.3.46(1)-release and sed is version 4.2.2
I tried both basedir types on my Cygwin dash 0.5.8-3 and they both worked.
On Ubuntu, the OP has a different problem: /usr/bin/env: 'node\r': No such file or directory. That looks like a line-ending issue to me, probably different from the Debian issue.

Bash complains "have not a command" when login

I have a file named "upstart" in /etc/bash_completion.d/ with the following content:
# bash-completion for the upstart event-based init replacement
# (http://upstart.ubuntu.com / https://launchpad.net/upstart)
#
# We don't provide completion for 'init' itself for obvious reasons.
have initctl &&
_upstart_jobs()
{
initctl list|awk '{print $1}'|sort -u
} &&
The confusing part is the line have initctl &&, I have configured bash to source
all files in /etc/bash_completion.d/ and every time I login it complains
that command have cannot be found. What is that line for?
Short Answer: maybe you should install bash-completion package.
In general, you don't need to manually source the files in /etc/bash_completion.d/.
It is automatically imported by bash_completion script.
bash_completion script is called by /etc/bash.bashrc (at least for Ubuntu and Arch Linux), which would be called by default.
In Ubuntu, bash_completion script is in bash-completion package and placed in /etc/bash_completion
In Arch Linux, bash_completion script is in bash-completion package and placed in /usr/share/bash-completion/bash_completion
Therefore, installing bash-completion script would help if you are using Ubuntu or Arch Linux.

Resources