Is there a way to get the name of the npm script passed to the command specified by that script? - npm

With npm or yarn, is it possible for the script specified by an npm script to know the name of the npm script itself? For example:
"scripts": {
"foo": "echo Original command: $0",
"bar": "echo Original command: $0"
}
I'd like the result of those two scripts to be something like:
Original command: yarn run foo
Original command: yarn run bar
But all I actually get is: Original command: /bin/sh.
And in case it makes a difference, it's just the name of the script I need, not the yarn run part, so output like Original command: foo would be fine.

NPM adds the npm_lifecycle_event environment variable. It's similar to package.json vars.
*Nix (Linux, macOS, ... )
On *nix platforms npm utilizes sh as the default shell for running npm scripts, therefore your scripts can be defined as:
"scripts": {
"foo": "echo The script run was: $npm_lifecycle_event",
"bar": "echo The script run was: $npm_lifecycle_event"
}
Note: The dollar prefix $ to reference the variable.
Windows:
On Windows npm utilizes cmd.exe as the default shell for running npm scripts, therefore your scripts can be defined as:
"scripts": {
"foo": "echo The script run was: %npm_lifecycle_event%",
"bar": "echo The script run was: %npm_lifecycle_event%"
}
Note: The leading and trailing percentage sign % used to reference the variable.
Cross-platform (Linux, macOS, Windows, ... )
For cross-platform you can either:
Utilize cross-var to enable a single syntax, i.e. using the dollar sign prefix $ as per the *nix syntax.
Or, utilize the node.js command line option -p to evaluate and print the result of the following inline JavaScript:
"scripts": {
"foo": "node -e \"console.log('The script run was:', process.env.npm_lifecycle_event)\"",
"bar": "node -e \"console.log('The script run was:', process.env.npm_lifecycle_event)\""
}
Note In this example we:
Access the npm_lifecycle_event environment variable using the node.js process.env property.
Utilize console.log (instead of echo) to print the result to stdout

Related

Does package.json support compound variables?

A project that respects the semver directory structure for build artefacts is beginning soon, and package.json or .nmprc would seem to be the right place to define this metadata. This is some preliminary code that demonstrates how the goal is intended to be achieved:
{
"name": "com.vendor.product"
, "version": "0.0.0"
, "directories": {
"build": "./out"
}
, "main": "./${npm_directories_build}/${npm_package_name}/${npm_package_version}/${npm_package_name}.js"
, "exports": "${npm_package_main}"
, "scripts": {
"echo": "echo\"${npm_package_exports}\""
}
}
I expected
npm run echo
to print the compound variable result to standard output,
./out/com.vendor.product/0.0.0/com.vendor.product.js
but instead, it prints the literal text
${npm_package_export}
I attempted to use array variables in .npmrc
outpath[]=./out
outpath[]=/${npm_package_name}
outpath[]=/${npm_package_version}
But
...
{
"echo": "echo \"${npm_config_outpath}\""
}
Simply prints an empty newline
It was expected that package.json supports compound variables, but this assumption is now in question. I have checked documentation, but either I am missing something or such is not defined. Long hand repetition of the same data is to be avoided (e.g. multiple references to package variables in order to make a single path). It is intended for package name and version to always dictate the location of the build files in a reliable and predictable manner.
If compound variables are not supported, could you clarify how .npmrc array variables actually work? Failing that, could you recommend an alternative method to achieve the same ends? Many thanks!
Searched documentation:
https://docs.npmjs.com/misc/config
https://docs.npmjs.com/files/npmrc
https://docs.npmjs.com/configuring-npm/npmrc.html
https://docs.npmjs.com/files/package.json#config
http://doc.codingdict.com/npm-ref/misc/config.html#config-settings
https://github.com/npm/ini
Short Answer:
"Does package.json support compound variables?"
Unfortunately no, not for the way you are wanting to use them. It only has package json vars which can be used in npm scripts only. For example on *Nix defining the echo script as:
"echo": "echo $npm_package_version"
or on Windows defining it as:
"echo": "echo %npm_package_version%"
will print the version e.g. 0.0.0.
Note: cross-env provides a solution for a single syntax that works cross-platform.
You certainly cannot include parameter substitution ${...} elsewhere in package.json fields except for the scripts section.
Additional info:
Regarding your subsequent comment:
How array values defined in .npmrc can be used in package.json
AFAIK I don't think you can. For example let's say we;
Save this contrived .npmrc in the root of the project directory.
.npmrc
quux[]="one"
quux[]="two"
quux[]="three"
foo=foobar
Then cd to the project directory and run the following command to print all environment variables:
npm run env
As you can see, the npm_config_foo=foobar environment variable has been added by npm. However for the quux array there is no npm_config_quux=[ ... ] environment variable added.
So, in npm scripts using package.json vars the following does work:
"echo": "echo $npm_config_foo"
However the following, (for referencing the array), does not - simply because it does not exist;
"echo": "echo $npm_config_quux"
The ini node.js package:
Maybe consider investigating the ini node.js package that npm utilizes for parsing .npmrc files. For example:
If you run the following command to install the package in your project:
npm i -D ini
Then define the npm echo script as per this:
"scripts": {
"echo": "node -e \"var fs = require('fs'), ini = require('ini'); var config = ini.parse(fs.readFileSync('./.npmrc', 'utf-8')); console.log(config.quux)\""
}
Note it uses the nodejs command line option -e to evaluate the JavaScript code. It essentially executes the following:
var fs = require('fs'),
ini = require('ini');
var config = ini.parse(fs.readFileSync('./.npmrc', 'utf-8'));
console.log(config.quux);
Then given the contrived .npmrc file that I mentioned previously when running:
npm run echo
it will print:
[ 'one', 'two', 'three' ]

Pass git commit message to npm script and append to predefined string

In an NPM project, I'd like to have a commit for each build version. This will allow me to go back to the current build version, fix a bug, without having to go through all the QA of a new version.
We can commit using npm scripts like this (see this answer):
package.json
"scripts": {
"git": "git add . && git commit -m",
}
Then invoke the script by running:
npm run git -- "Message of the commit"
I'd like to automate it to run after npm run build. For this purpose we can create a new command.
package.json
"scripts": {
"buildAndCommit": "npm run build && git add . && git commit -m",
}
This could be run using npm run buildAndCommit -- "commit for a new build"
The only thing left is that I'd like to identify this commit as one that could be linked to a commit. Is it possible to start the message automatically with "BUILD -" and to add to that the unique message which is passed in the command line? Something like:
package.json
"scripts": {
"buildAndCommit": "npm run build && git add . && git commit -'Build' + $uniqueMessageFromTheCommandLine`",
}
If it is not possible to template the string in package.json, how could I achieve it using a command line script? (Powershell is my command line tool).
Running on *nix platforms
On a *nix platform npm utilizes sh by default to execute the npm script(s). In this scenario you can simply use a shell function and reference the git message argument passed via the CLI using the $1 positional parameter.
Your npm script would be redefined like this:
"scripts": {
"build": "...",
"buildAndCommit": "func() { npm run build && git add . && git commit -m \"BUILD - $1\"; }; func"
}
Cross platform
Unfortunately, via Windows Powershell the solution is not quite as simple and terse.
When using Powershell npm utilizes cmd by default to execute npm script(s). Likewise npm utilizes cmd by default via other Windows consoles too, such as Command Prompt.
One way to achieve your requirement is to invoke a node.js via your npm script. The following provides two different methods that are essentially the same. Either will run successfully cross-platform (in your case via Powershell).
Method A - Using a separate node.js script
Create the following node.js script. Let's name the file script.js and save it in the root of the project directory, i.e. in the same directory where package.json resides.
script.js
const execSync = require('child_process').execSync;
const mssg = 'BUILD - ' + process.argv[2];
execSync('npm run build && git add . && git commit -m \"' + mssg + '\"', { stdio:[0, 1, 2] });
Explanation
The node.js builtin process.argv captures the argument at index two, i.e. the git commit message, that was provided via the CLI. The git commit message is concatenated with with the substring BUILD - to form the desired commit message. The resultant string is assigned to the variable mssg.
We then utilize the node.js builtin execSync() to execute your given npm script. As you can see, the value of the mssg variable is used as the git commit message.
The stdio option is utilized to ensure the correct configuration of the pipes, i.e. stdin, stdout, 'stderr', are established between the parent and child process.
Define your npm script named buildAndCommit as follows:
package.json
"scripts": {
"build": "...",
"buildAndCommit": "node script"
}
Above node invokes script.js.
Method B - Inline the node.js script in npm script
Alternatively, the aforementioned node.js script (i.e. script.js) can be provided inline in your npm script instead - therefore negating the use of a separate .js file.
package.json
"scripts": {
"build": "...",
"buildAndCommit": "node -e \"const mssg = 'BUILD - ' + process.argv[1]; require('child_process').execSync('npm run build && git add . && git commit -m \\\"' + mssg + '\\\"', { stdio:[0, 1, 2] })\""
}
This utilizes the same code from Method A albeit it slightly refactored. The notable differences are:
The nodejs command line option -e is utilized to evaluate the inline JavaScript.
process.argv this time will capture the argument, i.e. the git commit message, at index one in the array of arguments.
Additional escaping of the double quotes is necessary, i.e. \\\"
Running the npm script
Using either Method A or Method B run the command via your CLI as desired: For instance:
$ npm run buildAndCommit -- "commit for a new build"
This will produce the following git commit message:
BUILD - commit for a new build

Assign a random number (UUID) value to a environment variable in npm script

I'd like to add a UUID argument when calling my npm script. Each time the script is called, I'd like to generate a new number. It should look like:
"build": "cross-env UUID=unique_number ng build"
The only thing I need is generating the unique_number here. I tried to use the uuid package but I don't know how to fire it in the script and pass the number as the argument.
tl;dr As you're question shows the use of cross-var I've assumed a cross-platform solution is required. In which case refer to the Solution A. However refer to either Solution B or C if my assumption is incorrect.
Solution A: Cross Platform (Windows/Linux/macOS...)
Fo a cross platform solution, (i.e. one that runs successfully on Windows, Linux, and macOS...), you'll need to utilize nodejs to achieve your requirement. There are a couple of different ways to approach this as described in the following two sub-sections titled:
Using an external nodejs (.js) file
Inlining your JavaScript in package.json.
Note both approaches are effectively the same
Using an external nodejs (.js) file
Create a nodejs utility script. Let's name the file run-ng-build.js and save it in the root of your project directory, i.e. in the same directory where package.json currently resides:
run-ng-build.js
const uuid = require('uuid/v1');
const execSync = require('child_process').execSync;
process.env.UUID = uuid();
execSync('ng build', { stdio: [0, 1, 2]} );
In the scripts section of your package.json replace your current build script with the following:
package.json
"scripts": {
"build": "node run-ng-build"
}
Explanation:
In run-ng-build.js we require the uuid package and the nodejs built-in execSync().
To create the environment variable named UUID we utilize the nodejs builtin process.env, and assign a uuid value to it by invoking uuid().
We then invoke the ng build command using execSync.
The options.stdio option configures the pipes between the parent and child process - [0, 1, 2] effectively inherit's stdin, stdout, and stderr.
Inlining your JavaScript in package.json.
Alternatively, you can inline your nodejs/JavaScript code in the scripts section of your package.json.
In the scripts section of your package.json replace your current build script with the following instead:
package.json
"scripts": {
"build": "node -e \"process.env.UUID = require('uuid/v1')(); require('child_process').execSync('ng build', { stdio: [0, 1, 2]} );\""
}
Explanation:
This is effectively the same as the aforementioned solution that utilized a separate .js file, however the use of a separate nodejs script/file is now redundant.
The nodejs command line option -e is utilized to evaluate the inline JavaScript.
Important The cross-env package is redundant utilizing either of the two aforementioned solutions. To uninstall it run: npm un -D cross-env via your CLI.
Solution B: *Nix platforms only (Linux/MacOS...)
For *nix platforms only it becomes very terse, you can just define your build script in package.json as follows:
package.json
"scripts": {
"build": "cross-env UUID=$(uuid) ng build"
}
This utilizes a Bash feature known as command substitution, i.e. $(uuid). However, if *nix is the only platform you need to support, then cross-env is really not necessary. Use the built-in export feature instead. For instance:
package.json
"scripts": {
"build": "export UUID=$(uuid) && ng build"
}
Solution C: Windows only (cmd.exe)
On Windows (only) running via Command Prompt or PowerShell you can do the following:
package.json
"scripts": {
"build": "FOR /F %U IN ('uuid') DO cross-env UUID=%~U node -e \"process.env.UUID = require('uuid/v1')(); require('child_process').execSync('ng buuld', { stdio: [0, 1, 2] });\""
}
This is similar to the first example shown in Solution B however command substitution is achieved (very) differently in cmd.exe. See this answer for further explanation.

Passing argument to the middle of an npm script

Title says it all. I want to be able to pass the argument to the middle of an npm script so that I may do the following.
$ npm run deploy -- <destination-path>
In package.json
"scripts": {
"deploy": "robocopy dist <destination-path> /E /NP"
}
Is this possible without using environment variables or npm's configuration variables?
Per Passing args into run-scripts #5518 it would appear that is it not possible to pass arguments to the middle of the script.
We are not going to support passing args into the middle of the script, sorry. If you really need this, write your test command using literally any of the command line parsers that anyone uses. (Minimist, dashdash, nopt, and commander all support this just fine.)
However, an alternative to this using the npm configuration block has been documented here. My implementation then looks like this:
"name": "foo"
"config": { "destination" : "deploy" },
"scripts": { "deploy": "robocopy dist %npm_package_config_destination% /E /NP" }
I can then override this on the command line and on my build server with:
npm run deploy --foo:destination=C:\path\to\deploy\dir
You can use an environment variable to set the destination path.
PATH=/path/to/file npm run deploy -- $PATH
or
export PATH=/path/to/file
npm run deploy -- $PATH
I have a different way to do that via shell.
If your npm command is:
"deploy": "robocopy dist ${1} /E /NP"
Where ${1} is the parameter you want to substitute.
Then wrap it in a function as follow:
"deploy": "func() { robocopy dist ${1} /E /NP";}; func"
then you can run a positional parameter substitution in shell as follow:
npm run deploy -- xyz
which would run
robocopy dist xyz /E /NP
And since this is a shell script, you can use default parameters as well:
"deploy": "func() { robocopy dist ${1:-xyz} /E /NP";}; func"
And you can use it as follows:
npm run deploy <==> robocopy dist xyz /E /NP
npm run deploy -- abc <==> robocopy dist abc /E /NP
You can make use of the arg structure of "sh -c". In my example below, I have to echo-feed the npm arg into a language parser. The argument for npm run foma <some word> will be in place of the $0:
"sayhello": "bash -c 'echo hello $0!"
A cross-platform solution I use is:
"arg-helper": "node -e \"process.stdout.write(require('child_process').execSync(process.argv[1].replace('$', process.argv[2] || '')))\"",
"sayhello": "npm run arg-helper \"echo hello $!\"
...
>npm run sayhello world
Levereging npm_config_* variables.
package.json
{
"scripts": {
"echoMyParam": "echo 'your param value is' $npm_config_foo"
}
}
Run
npm run echoMyParam --foo=bar
Result
your param value is bar
It's important to check the docs for other cases: https://docs.npmjs.com/using-npm/config

Unable to read parameters from package.json

I am unable to read parameters from package.json, so it's always $(directory).
and this is my command:
npm run migrate -- -directory "migration_dir"
package.json
{
"name": "XXXX",
"version": "0.1.0",
"description": "XXX",
"main": "app.js",
"scripts": {
"migrate": "cd $(directory)"
},
"keywords": [
"XXXX"
],
"author": "XXXX",
"license": "MIT"
}
Thank you.
The problem you may be facing is two-fold.
1. Arguments syntax for run-script
From npm docs:
As of npm#2.0.0, you can use custom arguments when executing scripts. The special option -- is used by getopt to delimit the end of the options. npm will pass all the arguments after the -- directly to your script:
npm run test -- --grep="pattern"
Arguments after -- will be passed directly, in your case that is
$ cd $(directory) -directory "migration_dir"
To fix that, you have to change your script definition accordingly:
"scripts": {
"migrate": "cd"
}
And pass the directory with no -directory option:
$ npm run migrate -- "migration_dir"
This will execute
$ cd "migration_dir"
2. Scripts are executing in a subshell
The migrate script you posted is useless because all npm scripts are executed in subshells, and changing the working directory from inside a subshell doesn't make a difference when a subshell exits.
Although if your script does anything beyond cd that will work:
"scripts": {
"foo": "cd / && pwd"
}
Part of the script after && will see the effect of cd, but your shell won't.
$ npm run foo
> cd / && pwd
/
$ pwd
/your/original/path
However, you can't specify additional arguments using -- anymore (npm run foo -- arg will do cd / && pwd arg).
It's look like You want to read a ENV parameter. Bash notation is ${directory} not $(directory).
Now You can run migration
directory="migration_dir" npm run migrate
The output is
cd migration_dir
Pros of this solution is possibility of use complicated scripts.