Skip to content
This repository has been archived by the owner on Aug 11, 2022. It is now read-only.

pass cli args anywhere into an npm run scripts command with possible re-use #9627

Closed
serapath opened this issue Sep 17, 2015 · 19 comments
Closed

Comments

@serapath
Copy link

I found an already closed issue: #3494

I'm using browserify and a lot of different transforms and plugins to build my project and start all kinds of dev servers to help me with my work.

see: https://github.com/substack/browserify-handbook#plugins

I need to pass in arguments and sometime, they cannot be hard coded, like my current local area network ip adress, e.g. 192.168.2.112

using -- -u 192.168.2.112 won't help, because it needs to be inside of ... -p [ browserify-hmr -u 192.168.2.112 ] ....

What can I do?

@serapath
Copy link
Author

ok, i found a solution. I can wrap whatever i need to pass into an npm script command into a cli tool, that console.log(whatever), and add $(subcommand to the npm script command.

In my particular case i used the my-local-ip module and rewrote the npm script to:

... -p [ browserify-hmr -u http://$(my-local-ip):3123 ] ...

If there is a better way, I'm curious to learn :-)

@sterpe
Copy link

sterpe commented Sep 17, 2015

I would check out package.json config field:

config

A "config" object can be used to set configuration parameters used in package scripts that persist across upgrades. For instance, if a package had the following:

{ "name" : "foo"
, "config" : { "port" : "8080" } }

and then had a "start" command that then referenced the npm_package_config_port environment variable, then the user could override that by doing npm config set foo:port 8001.

See npm-config(7) and npm-scripts(7) for more on package configs.

https://docs.npmjs.com/files/package.json#config

@serapath
Copy link
Author

I tried to use npm config set ..., but it changed my global .npmrc file and that's a no go for me.
Hard coding my local ip into package.json might work as long as my ip does not change a lot, but every time someone else uses the module, he has to edit and update package.json, which feels wrong.

For now, creating a custom module which console.logs the information i need in order to use it inline in an npm scripts task seems to be the best, but i guess this might not work cross-platform, especially, I have my doubts this would work on windows :/

@prabhg
Copy link

prabhg commented Sep 28, 2015

+1 to the request. I use npm scripts to run specific unit test spec. I want to be able to pass the path to test-file as argument.

@ahdinosaur
Copy link

how about bash -c 'command ${1} | other-command' 0 first-arg?

@jasonkarns
Copy link
Contributor

@serapath the config block of package.json can be overridden in a number of ways without setting global .npmrc settings. see https://docs.npmjs.com/misc/config

Assuming a package.json similar to this:

name: "foo",
config: { myIP: "baz" },
scripts: { build: "browserify -p [ browserify-hmr -u http://$npm_package_config_myIP:3123 ]" }

They can be overridden via:

  • the command line
    npm run build --foo:myIP=123
  • local .npmrc file
    foo:myIP=123
  • global ~/.npmrc file (or wherever global config is located per $NPM_CONFIG_USERCONFIG)
    foo:myIP=123

@ruimarinho
Copy link

@jasonkarns did you mean $npm_config_myIP? On npm@2 that's what I believe to be the correct variable name. Also, --foo-bar expands to $npm_config_foo_bar and a space is not allowed to assign a value (--foo-bar value1 will not work, while --foo-bar=value2 will).

@jasonkarns
Copy link
Contributor

did you mean $npm_config_myIP

No. npm_config_* doesn't respect package.json config property, only npm_package_config_* does. I should have been more specific with the link I sent:

https://docs.npmjs.com/files/package.json#config
https://docs.npmjs.com/misc/config#per-package-config-settings
https://docs.npmjs.com/misc/scripts#special-package-json-config-object

(And I conflated the two in my examples. the ENV var is for npm_config, not npm_package_config. I have removed the ENV var example.)

The rest of my examples are exactly correct (and tested) as written.

The config object from package.json is flattened into env vars named npm_package_config_X. If the properties of the config object are also objects, they are all flattened/joined with underscores.

"config": {
  "foo": {
    "bar": "baz"
  }
}

is available as $npm_package_config_foo_bar -> "baz"

And the package.json config object is overridden by global and local .npmrc files where the config settings are <package_name>:<config_property_name>=<value>.

And the npmrc files are overridden from the command line with --<project_name>:<config_property_name>=<value>

@ruimarinho
Copy link

Ah, right, I was actually mentioning the part of custom argument position via cli for running npm scripts, so script "foo": "cmd1 $npm_config_bar && cmd2" would be configurable with npm run foo --bar=qux in runtime (the output would be cmd1 "qux" && cmd2).

@jasonkarns
Copy link
Contributor

Gotcha. The npm_config variant is much nicer when overriding via the command line (and via npmrc files). The downside is that one can't set defaults in package.json that way. Personally, I find npm_package_config_* settings preferable for use by npm-scripts (since defaults can be set in package.json itself, and overridden via CLI or npmrc files). And I prefer the npm_config_* variant preferable for variables that will be referenced from within JS itself (via process.env.), since that allows the default to be specified in code at that point. (While still allowing CLI and npmrc overrides)

@dogancelik
Copy link

According to #3494 we can't use $1 (Unix) or %1 (Windows)

I want to be able to pass arguments without having to use Bash or Batch scripts

{
  "scripts": {
    "zip-base": "7za (some zipping commands here) %1",
    "zip-osx": "npm run zip-base -- darwin",
    "zip-linux": "npm run zip-base -- linux",
    "zip-win": "npm run zip-base -- win32",
    "zip-all": "npm run zip-osx && npm run zip-linux && npm run zip-win"
  }
}

Maybe $npm_run_arg1 or something like that?

@elisechant
Copy link

try

import { argv as args } from 'yargs';

"test": "babel-node ./tools/test.js --m=$MODULE",

which logs
{ _: [], m: 'sdjhfg', '$0': 'tools/test.js' }

access with

args.m

execute on cli with

MODULE=module_1 npm run test

@AlexFrazer
Copy link

Thanks, @jasonkarns, that helped with my problem.

Another solution is to manually create a script which programmatically spawns the process with the arguments. Eg:

const childProcess = require('child_process');
const args = process.argv.slice(2);
childProcess.spawn(`yourscript ${args[1] || 'blah'}`);

@jasonkarns
Copy link
Contributor

@AlexFrazer another option would be to use scripty

@npm-robot
Copy link

We're closing this support issue as it has gone three days without activity. The npm CLI team itself does not provide support via this issue tracker, but we are happy when users help each other here. In our experience once a support issue goes dormant it's unlikely to get further activity. If you're still having problems, you may be better served by joining package.community and asking your question there.

For more information about our new issue aging policies and why we've instituted them please see our blog post.

@refactorized
Copy link

refactorized commented Aug 1, 2017

This should be better supported by npm, but here is a workaround for arbitrarily positioned arguments in an npm script:

package.json file:
(https://gist.github.com/refactorized/60eb05c144d3fde4406437ae669330c0)

{
  "name": "npm-args",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "hbd": "bash -c 'echo \"happy birthday $0! and many returns\"'", // <--- here
    "paradiddle": "bash -c 'echo \"$0$1$0$0  $1$0$1$1\"'",               // <- here too
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

which is called like this:

$npm run paradiddle -- 'R ' 'L '

and returns

> npm-args@1.0.0 paradiddle /Users/tolleya0-v/code/npm-args
> bash -c 'echo "$0$1$0$0  $1$0$1$1"' "R " "L "

R L R R   L R L L

note that if you may not always need the -- but should probably keep it there for clarity.

@brainthinks
Copy link

brainthinks commented Oct 23, 2017

Thank you @refactorized , I was able to get a script that I liked with your advice. Here is what I came up with:

{
  "scripts": {
    "hbd": "bash -c 'echo \"happy birthday ${1-Brian}! and many returns\"' -- ",
  }
}

The difference is that my script has -- at the end.

My npm script needed to provide a default value if no arguments were passed. Using my hbd script without the two dashes at the end resulted in inconsistent results when running the script with and without the dashes. If I didn't include the dashes, the variable would always be "bash", rather than nothing, which means I couldn't supply my own default value. However, by putting the dashes at the end of the npm script itself, my default value logic works. See here for information on the default value logic:

https://www.cyberciti.biz/faq/bash-ksh-if-variable-is-not-defined-set-default-variable/

Now, I can run both of these commands:

npm run hbd // outputs 'happy birthday Brian! and many returns'
npm run hbd Courtney // outputs 'happy birthday Courtney! and many returns'

I wanted to provide my working example in case someone else was in a situation where the command had to be executed in that way.

However, I ended up using a different strategy - instead of getting npm run to pass the variable to me, I specified an environment variable. So even though the above script works, I found its readability too low to actually use in my project. Here is the script I ended up using:

{
  "scripts": {
    "hbd": "echo \"happy birthday ${NPM_BIRTHDAY_NAME-Brian}! and many returns\"",
  }
}

Here are what my commands look like now:

npm run hbd // outputs 'happy birthday Brian! and many returns'
NPM_BIRTHDAY_NAME=Courtney npm run hbd // outputs 'happy birthday Courtney! and many returns'

It makes the command itself heavier, but it's easy enough to understand, and keeps the underlying logic simpler.

@jasonkarns
Copy link
Contributor

bash -c requires the -- because of the following (from bash's manpage):

If the -c option is present, then commands are read from the first non-option argument command_string. If there are arguments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.

Another way to get positional (numbered) shell variables is to use a function.

    "foo": "f() { echo $1; }; f"

first we define a function f, whose body is whatever command or commands you wish to use, accepting positional arguments. immediately after the function declaration, the function f is invoked. Since npm concatenates extra args to the end, the result is f() { ...; }; f arg1 arg2 etc. Thus $1 and $2 would be "arg1" and "arg2" respectively. This approach doesn't have the limitation of the extra -- as the bash -c approach.

This technique is also useful for making git "subcommands" via aliases that can accept leverage positional args:

https://github.com/jasonkarns/dotfiles/blob/ca8e0b678f2c004ad698280b0e02ffb4f5d74e6d/.config/git/config#L47

@Download
Copy link

Unfortunately most mentioned workarounds are bash specific and not cross-platform.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests