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
Related
I'm trying to use echo -n command to write some content to a file without a new line in node's package.json e.g
start: "echo -n hello > my.file"
When I run npm start, instead of hello without a new line, it creates -n hello in my.file. However it runs correctly in my Terminal(I'm on Mac).
Depending of your context, you can use printf instead of echo, which does not append a \n char automatically.
https://unix.stackexchange.com/questions/58310/difference-between-printf-and-echo-in-bash
This is due to NPM running scripts with the sh(1) shell, which does not accept the -n option. One way of solving this would be to set NPM to use Bash:
npm config set script-shell "/bin/bash"
Documentation for echo command
Some shells may provide a builtin echo command which is similar or identical to this utility. Most notably, the builtin echo in sh(1) does not accept the -n option. Consult the builtin(1) manual page.
Documentation for NPM run-scripts
The actual shell your script is run within is platform dependent. By default, on Unix-like systems it is the /bin/sh command, on Windows it is the cmd.exe. The actual shell referred to by /bin/sh also depends on the system. As of npm#5.1.0 you can customize the shell with the script-shell configuration.
Here's also a related StackOverflow answer: "echo -n" works fine when executing script with bash, but not with sh
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.
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.
I'm writing a nodejs commandline tool. When I test by running npm install and trying to run it locally, npm creates the following files in node_modules/.bin:
node_modules/.bin/myapp containing:
"$basedir/../myapp/dist/myappcli.js" "$#"
exit $?
node_modules/.bin/myapp.cmd containing:
"%~dp0\..\myapp\dist\myappcli.js" %*
They aren't very useful.
On the other hand, when I install popular nodejs cli tools, such as js-yaml or typings, I get much more useful files:
node_modules/.bin/typings containing:
#!/bin/sh
basedir=`dirname "$0"`
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../typings/dist/bin.js" "$#"
ret=$?
else
node "$basedir/../typings/dist/bin.js" "$#"
ret=$?
fi
exit $re
node_modules/.bin/typings.cmd containing:
#IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\typings\dist\bin.js" %*
) ELSE (
#SETLOCAL
#SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\typings\dist\bin.js" %*
)
These actually successfully run their apps. Why do they get this fancy stuff? How can I make my app provide this? Do I have to manually add files into a tgz before uploading it to an npm host? I hope not, because I want to be able to install my app straight from github, or at least have npm pack generate the final, complete tgz.
Thanks
Add the following line to the very top of the javascript file which serves as the entrypoint for your command line application:
#!/usr/bin/env node
Thank you to jmir for this answer.
I am trying to run a global install of a locally created package.
npm install -g myModule
which seems to work.... for the most part.
I see the files I would expect in dir: Users\me\AppData\Roaming\npm\ (Windows)
I see a batch file and a cmd file.
But both have code like:
"$basedir/node_modules/myModule/main.js" "$#"
exit $?
I look around the dir, and see Mocha, Bower, Grunt have something like:
#!/bin/sh
basedir=`dirname "$0"`
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/node_modules/bower/bin/bower" "$#"
ret=$?
else
node "$basedir/node_modules/bower/bin/bower" "$#"
ret=$? fi exit $ret
If I change files to have this format (and adjusts paths) I get the expected behavior (can run myModule from command line)....
Otherwise I get error: ... No such file or directory
so it turns out this is by design.
I resolved it by doing the following
starting my entry point file with #!/usr/bin/env node
and removing the .js extension on the file.
setting bin on package.json to that file:
"bin": {
"myModule": "bin/myModule"
}