How to fetch package version and date in npm script? - node.js

I have a package.json file and I have this script to create 2 separate files one for version and other one for date.
When i run the command, it generates the version-npm.txt with correct data holding the current version, but the version.txt file is generated with the exact script instead, it contains date +"%d/%m/%Y %T"
"scripts": {
"versions": "node -e \"console.log(require('./package.json').version);\" > ./public/version-npm.txt && echo `date +\"%d/%m/%Y %T\"` > ./public/version.txt"
}
I need this to be updated with the current date and time. is there any other way to it or can you help me fix it?
i tried writing the date as \"$(date)\" still the same, now i get \"$(date)\"

Why is it not working?
The way that you are obtaining the date, i.e. date +\"%d/%m/%Y %T\" or similarly using \"$(date)\" does work successfully on *nix platforms, whereby npm utilizes sh by default to run npm scripts.
However, the aforementioned method does not work on Windows because npm on that OS utilizes cmd as the default shell to run npm scripts - cmd simply does not understand the date command.
Solution
The following solution will work cross-platforms (Windows, Linux, MacOS etc) :
Firstly cd to your project directory and install the moment package by running the following command:
npm i -D moment
We'll utilize this package to obtain the date/time formatted as DD/MM/YYY HH:MM:SS.
Then redefine your versions script in the scripts section of your package.json as follows:
"scripts": {
"versions": "node -p \"process.env.npm_package_version\" > ./public/version-npm.txt && node -p \"require('moment')().format('MM/DD/YYYY HH:mm:ss')\" > ./public/version.txt"
}
Explanation:
The npm script (above) utilizes the nodejs command line option -p to evaluate and print the result of the following inline JavaScript:
process.env.npm_package_version
This essentially utilizes nodejs process.env to read the environment variable npm_package_version which npm creates. See my answer here for further explanation.
The version is then redirected (>) to the file using the same method as per your attempt:
> ./public/version-npm.txt
Note: You could continue to utilize your current, more verbose, solution to obtain the version from package.json if you prefer, i.e.
node -e \"console.log(require('./package.json').version);\"
Next we obtain the date. Again we utilize the nodejs command line option -p to evaluate and print the result of the following inline JavaScript:
require('moment')().format('MM/DD/YYYY HH:mm:ss')
The date value is redirected (>) to the file as follows:
> ./public/version.txt

Related

Using glob in Yarn/NPM script

I'm using a script to run multiple files with Node.js using a glob pattern to capture the files:
node build/**/*.spec.js
This seems to work fine, but when I put the same command in the scripts object as follows
"scripts": {
"test": "node build/**/*.spec.js"
}
and try to run it with yarn test or npm run test I get the following error:
Error: Cannot find module '/path/to/project/build/**/*.spec.js'
indicating that it's treating the glob pattern as a literal filename.
How can I achieve the glob behaviour in a Yarn/NPM script?
A couple of things to note here:
The behaviour of node build/**/*.js depends on your shell.
In zsh (the current default on macOS), it does what you expect; expanding the glob to all matching files and passing them to Node.
In bash and sh, it tries to run the literal file build/**/*.js with Node, which gives the "Cannot find module" error in your post.
The default shell used by npm run-script is, per the docs:
'/bin/sh' on POSIX systems
Even if you're using zsh, when you run the test script it uses sh. You can configure this with the script-shell option, e.g.:
in .npmrc (which you can update with npm config set script-shell=/bin/zsh); or
inline (by running npm --script-shell=/bin/zsh test).
However you get it running, note that only one of the matched files actually gets executed; the paths to the other two are passed as command line arguments. With three files foo.spec.js, bar.spec.js and baz.spec.js containing the following (adjusted to log each file's own name) in build/:
console.log(process.argv);
console.log("foo");
running the globbed version gives:
$ node build/**/*.spec.js
[
'<path/to>/bin/node',
'<path/to>/build/bar.spec.js',
'build/baz.spec.js',
'build/foo.spec.js'
]
bar

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?

How do I time my npm install?

I am trying to see how much time npm install takes to complete installing all my dependencies. Is there a way for me to time this process, natively (in npm) or using some third party plugin? I've tried
* npm i --verbose
* npm i --silly
* slow-deps (A third party lib that analyzes your npm packages)
slow-deps gave me a decent result, but I am not sure about it's accuracy, as it doesn't run as part of the npm install process.
Is there any other way to accurately time-profile the npm install process? I'm looking for an output like this (output screenshot from Yarn):
Thanks for your help. I have come across a few utilities for this:
The time utility mentioned by #JJJ in the comments section.
Paypal's gnomon package, that updates timestamps to everything.
Use time linux command and have the following command:
time npm install
Also according to https://www.commandlinux.com/man-page/man1/time.1.html
Users of the bash shell need to use an explicit path in order to run the external time command and not the shell builtin variant.
so your command may be (I am a macOS user and I am using the below command):
/usr/bin/time npm install
The output of this command (at the last line) would be something like this:
153.96 real 67.63 user 34.12 sys
so basically what you are looking at is the real number that are in seconds. To the above example the time taken is:
153.96s

How to use curly brackets in npm scripts?

I want to run this simple command in an npm script called prepare_build:
...
scripts: {
"prepare_build" : "mkdir -p dist/lib/{js,css}"
}
...
running npm run prepare_build would create a single directory dist/lib/{js,css} whereas running the command in the console would create both dist/lib/js and dist/lib/css.
What am I doing wrong here?
Note:
I am aware of Gulp, Grunt, and other build systems WHICH I don't want to use
OS Interoperability is not of concern here. Linux is the main target.
The {js,css} syntax is neither a feature of mkdir, nor is it a feature of the OS. It is a feature of your shell (probably bash). So in order to interpret it you need to execute a shell that can understand that syntax.
The following will probably work but is untested:
scripts: {
"prepare_build" : "bash -c 'mkdir -p dist/lib/{js,css}'"
}
This executes bash which transforms the command into:
mkdir -p dist/lib/js dist/lib/css
before executing it.
When using the npm scripts there tend to be certain quirks that occur, especially when trying to create npm scripts that work on Win, Linux and Mac. It is often most pragmatic to find workarounds. In your case I would recommend installing the mkdirp package npm install mkdirp --save-dev and then the following will work:
scripts: {
"prepare_build" : "mkdirp dist/lib/js dist/lib/css"
}

Resources