如何将命令行参数传递给从可执行脚本启动的NodeJS

时间:2021-05-04 00:38:39

How to set what would otherwise be command-line arguments to node for a NodeJS process run from a launcher script? (The sh/CMD scripts npm places into node_modules/.bin.)

如何为从启动脚本运行的NodeJS进程设置命令行参数?(sh/CMD脚本npm放在node_modules/.bin中。)

Plenty of NodeJS libraries / frameworks come with their own runner script, e.g. zeit/micro or moleculer that's usually executed from a npm script. This presents a problem in development, since in my case I want to do the equivalent of:

很多NodeJS库/框架都有自己的运行器脚本,例如zeit/micro或分子,它们通常是从npm脚本执行的。这给发展带来了一个问题,因为就我而言,我想做的相当于:

node --inspect -r ts-node/register -r dotenv-safe/config src/index.ts

(Except, of course, that does nothing since index.ts just exports something for the runner to pick up.)

(当然,除了索引之后什么都没有。it’它只是输出一些东西给跑步者去拿。

Is there some "clean", preferably generic (i.e. not specific to a given framework's runner exposing those command line params) way that I'm missing to do this, ideally one that works as a npm script? The only thing that seems like it would work would be for e.g. micro:

是否有一些“干净”的,最好是通用的(例如,不是特定于暴露这些命令行参数的给定框架的运行器),我没有这样做,最好是作为npm脚本工作的那种?唯一看起来会起作用的事情是:

node-dev -r ts-node/register ./node_modules/micro-dev/bin/micro-dev.js ./src/index.ts

which is kind of a mouthful from the Redundant Department of Redundancy Department and seems to obviate the point of having those launcher scripts. (It also won't work if the runner spawns other Node processes, but that's not a problem I'm actually having.) I'd like to not have to duplicate what the launcher scripts are already doing. I'm also aware of npx having --node-arg but npx is a whole another can of worms. (On Windows it's five seconds of startup time and one spurious error message just to run a script I already have installed; it also won't find an already installed package if it can't find its .cmd launcher script, e.g. when using Docker to run the dev environment. In short I'd rather not use npx for this.)

这有点像冗余部门的冗余,并且似乎排除了那些启动脚本的意义。(如果运行程序生成其他节点进程,它也不会工作,但这不是我实际遇到的问题。)我不需要复制启动脚本已经在做的事情。我也知道npx有,node-arg,但npx是另一种蠕虫病毒。(在Windows上,运行我已经安装的脚本需要5秒的启动时间和一条虚假的错误消息;如果它找不到它的.cmd启动脚本,比如使用Docker运行dev环境时,它也找不到已经安装的包。简而言之,我宁愿不使用npx。


To clear up the confusion that seems to crop up in the comments: I want to override the command line parameters that affect the behaviour of the NodeJS runtime itself executing the runner script, not pass parameters to the script itself or to my code. That is, the options listed here: https://nodejs.org/api/cli.html

为了消除注释中出现的混乱:我想重写影响NodeJS运行时本身执行runner脚本的行为的命令行参数,而不是将参数传递给脚本本身或我的代码。也就是说,这里列出的选项:https://nodejs.org/api/cli.html

3 个解决方案

#1


1  

One option is to write a little wrapper script that uses the current process execPath to run child_process.execFile.

一个选项是编写一个小的包装器脚本,该脚本使用当前进程execPath运行child_process.execFile。

So the sample here is to be able to do

这里的样本是可以做到的

node --expose-http2 --zero-fill-buffers -r ./some-module.js ./test.js

节点——expose-http2——zero-fill-buffer -r ./some-module。js / . js。

but not actually write that out, instead have wrap.js inject the args:

但不是把它写出来,而是用wrap。js注入参数:

node ./wrap.js ./test.js

节点。/包装。js / . js。

I tested running this via npm in a package.json, and it works fine. I tested that it was working by having some-module.js stick a value on the global object, and then logging it in test.js.

我在一个包中测试了如何通过npm运行它。json,效果很好。我通过一个模块来测试它是否正常工作。js在全局对象上粘贴一个值,然后在test.js中记录它。

Files involved:

文件包括:

wrap.js

wrap.js

const child_process = require('child_process');

const nodeArgs = ['--expose-http2', '--zero-fill-buffers', '-r', './some-module.js'];
const runTarget = process.argv[2];

console.log('going to wrap', runTarget, 'with', nodeArgs);

const finalArgs = nodeArgs.concat(runTarget).concat(process.argv.slice(2));

const child = child_process.execFile(
  process.execPath,
  finalArgs,
  {
    env: process.env,
    cwd: process.cwd(),
    stdio: 'inherit'
  }, (e, stdout, stderr) => {
    console.log('process completed');
    if (e) {
      process.emit('uncaughtException', e);
    }
  });

child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);

and

some-module.js

some-module.js

global.testval = 2;

and

test.js

. js

console.log('hi guys, did the wrap work?', global.testval)

EDIT: So upon further thought, this solution really only satisfies wrapping the initial runner. But most tools, such as mocha re-spawn a sub process which would then lose this effect. To really get the job done, you can proxy each of the child process calls and somewhat enforce that calls to spawn and such also include your args.

编辑:经过进一步的思考,这个解决方案只满足最初的运行者。但是大多数工具,比如mocha,会重新生成一个子过程,然后就会失去这个效果。要真正完成这项工作,您可以代理每个子进程调用,并在某种程度上强制那些要派生的调用,这也包括您的args。

I rewrote the code to reflect this. Here's a new setup:

我重写了代码以反映这一点。这是一个新的设置:

package.json

package.json

{
  "scripts": {
    "test": "node -r ./ensure-wrapped.js node_modules/mocha/$(npm view mocha bin.mocha) ./test.js"
  },
  "dependencies": {
    "mocha": "^5.1.0"
  }
}

ensure-wrapped.js

ensure-wrapped.js

const child_process = require('child_process');

// up here we can require code or do whatever we want;

global.testvalue = 'hi there'
const customParams = ['--zero-fill-buffers'];

// the code below injects itself into any child process's spawn/fork/exec calls
// so that it propogates

const matchNodeRe = /((:?\s|^|\/)node(:?(:?\.exe)|(:?\.js)|(:?\s+)|$))/;
const ensureWrappedLocation = __filename;

const injectArgsAndAddToParamsIfPathMatchesNode = (cmd, args, params) => {
  params.unshift(...customParams);
  params.unshift(args);
  if (!Array.isArray(args)) { // all child_proc functions do [] optionally, then other params
    args = []
    params.unshift(args);
  }

  if (!matchNodeRe.test(cmd)) {
    return params;
  }

  args.unshift(ensureWrappedLocation);
  args.unshift('-r');

  return params;
}

child_process._exec = child_process.exec;
child_process.exec = (cmd, ...params) => {
  // replace node.js node.exe or /path/to/node to inject -r ensure-wrapped.js ...args..
  // leaves alone exec if it isn't calling node
  cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
  return child_process._exec(cmd, ...params)
}
child_process._execFile = child_process.execFile;
child_process.execFile = (path, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
  return child_process._execFile(path, ...params)
}
child_process._execFileSync = child_process.execFileSync;
child_process.execFileSync = (path, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
  return child_process._execFileSync(path, ...params);
}
child_process._execSync = child_process.execSync;
child_process.execSync = (cmd, ...params) => {
  cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
  return child_process._exec(bin, ...args)
}
child_process._fork = child_process.fork;
child_process.fork = (module, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(process.execPath, args, params);
  return child_process._fork(module, ...params);
}
child_process._spawn = child_process.spawn;
child_process.spawn = (cmd, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
  return child_process._spawn(cmd, ...params)
}
child_process._spawnSync = child_process.spawnSync;
child_process.spawnSync = (cmd, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
  return child_process._spawnSync(cmd, ...params);
}

test.js

. js

describe('test', () => {
  it('should have the global value pulled in by some-module.js', (done) => {
    if (global.testvalue !== 'hi there') {
      done(new Error('test value was not globally set'))
    }
    return done();
  })
})

Please never put code like this into a node module that's published. modifying the global library functions is pretty bad.

请不要将这样的代码放入已发布的节点模块中。修改全局库函数非常糟糕。

#2


1  

I didnt get clear scenario of your problem,but as your question title ,we can execute the any cmd command from nodejs using npm libraries like:

我没有弄清楚你的问题,但是作为你的问题标题,我们可以使用npm库执行nodejs的任何cmd命令,比如:

import Promise from 'bluebird'
import cmd from 'node-cmd'

const getAsync = Promise.promisify(cmd.get, { multiArgs: true, context: cmd })

getAsync('node -v').then(data => {
  console.log('cmd data', data)
}).catch(err => {
  console.log('cmd err', err)
})  

#3


0  

Everything passed in the command line AFTER your nodejs application is parsed into an array called process.argv. So...

在nodejs应用程序被解析为process.argv数组之后,命令行中传递的所有内容。所以…

node myapp.js foo bar hello 5000

In your nodejs code...

在nodejs代码…

const args = process.argv;
console.log(args[0]);
console.log(args[1]);
console.log(args[2]);
console.log(args[3]);

would yield...

将产生……

foo
bar
hello
5000

#1


1  

One option is to write a little wrapper script that uses the current process execPath to run child_process.execFile.

一个选项是编写一个小的包装器脚本,该脚本使用当前进程execPath运行child_process.execFile。

So the sample here is to be able to do

这里的样本是可以做到的

node --expose-http2 --zero-fill-buffers -r ./some-module.js ./test.js

节点——expose-http2——zero-fill-buffer -r ./some-module。js / . js。

but not actually write that out, instead have wrap.js inject the args:

但不是把它写出来,而是用wrap。js注入参数:

node ./wrap.js ./test.js

节点。/包装。js / . js。

I tested running this via npm in a package.json, and it works fine. I tested that it was working by having some-module.js stick a value on the global object, and then logging it in test.js.

我在一个包中测试了如何通过npm运行它。json,效果很好。我通过一个模块来测试它是否正常工作。js在全局对象上粘贴一个值,然后在test.js中记录它。

Files involved:

文件包括:

wrap.js

wrap.js

const child_process = require('child_process');

const nodeArgs = ['--expose-http2', '--zero-fill-buffers', '-r', './some-module.js'];
const runTarget = process.argv[2];

console.log('going to wrap', runTarget, 'with', nodeArgs);

const finalArgs = nodeArgs.concat(runTarget).concat(process.argv.slice(2));

const child = child_process.execFile(
  process.execPath,
  finalArgs,
  {
    env: process.env,
    cwd: process.cwd(),
    stdio: 'inherit'
  }, (e, stdout, stderr) => {
    console.log('process completed');
    if (e) {
      process.emit('uncaughtException', e);
    }
  });

child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);

and

some-module.js

some-module.js

global.testval = 2;

and

test.js

. js

console.log('hi guys, did the wrap work?', global.testval)

EDIT: So upon further thought, this solution really only satisfies wrapping the initial runner. But most tools, such as mocha re-spawn a sub process which would then lose this effect. To really get the job done, you can proxy each of the child process calls and somewhat enforce that calls to spawn and such also include your args.

编辑:经过进一步的思考,这个解决方案只满足最初的运行者。但是大多数工具,比如mocha,会重新生成一个子过程,然后就会失去这个效果。要真正完成这项工作,您可以代理每个子进程调用,并在某种程度上强制那些要派生的调用,这也包括您的args。

I rewrote the code to reflect this. Here's a new setup:

我重写了代码以反映这一点。这是一个新的设置:

package.json

package.json

{
  "scripts": {
    "test": "node -r ./ensure-wrapped.js node_modules/mocha/$(npm view mocha bin.mocha) ./test.js"
  },
  "dependencies": {
    "mocha": "^5.1.0"
  }
}

ensure-wrapped.js

ensure-wrapped.js

const child_process = require('child_process');

// up here we can require code or do whatever we want;

global.testvalue = 'hi there'
const customParams = ['--zero-fill-buffers'];

// the code below injects itself into any child process's spawn/fork/exec calls
// so that it propogates

const matchNodeRe = /((:?\s|^|\/)node(:?(:?\.exe)|(:?\.js)|(:?\s+)|$))/;
const ensureWrappedLocation = __filename;

const injectArgsAndAddToParamsIfPathMatchesNode = (cmd, args, params) => {
  params.unshift(...customParams);
  params.unshift(args);
  if (!Array.isArray(args)) { // all child_proc functions do [] optionally, then other params
    args = []
    params.unshift(args);
  }

  if (!matchNodeRe.test(cmd)) {
    return params;
  }

  args.unshift(ensureWrappedLocation);
  args.unshift('-r');

  return params;
}

child_process._exec = child_process.exec;
child_process.exec = (cmd, ...params) => {
  // replace node.js node.exe or /path/to/node to inject -r ensure-wrapped.js ...args..
  // leaves alone exec if it isn't calling node
  cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
  return child_process._exec(cmd, ...params)
}
child_process._execFile = child_process.execFile;
child_process.execFile = (path, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
  return child_process._execFile(path, ...params)
}
child_process._execFileSync = child_process.execFileSync;
child_process.execFileSync = (path, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
  return child_process._execFileSync(path, ...params);
}
child_process._execSync = child_process.execSync;
child_process.execSync = (cmd, ...params) => {
  cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
  return child_process._exec(bin, ...args)
}
child_process._fork = child_process.fork;
child_process.fork = (module, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(process.execPath, args, params);
  return child_process._fork(module, ...params);
}
child_process._spawn = child_process.spawn;
child_process.spawn = (cmd, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
  return child_process._spawn(cmd, ...params)
}
child_process._spawnSync = child_process.spawnSync;
child_process.spawnSync = (cmd, args, ...params) => {
  params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
  return child_process._spawnSync(cmd, ...params);
}

test.js

. js

describe('test', () => {
  it('should have the global value pulled in by some-module.js', (done) => {
    if (global.testvalue !== 'hi there') {
      done(new Error('test value was not globally set'))
    }
    return done();
  })
})

Please never put code like this into a node module that's published. modifying the global library functions is pretty bad.

请不要将这样的代码放入已发布的节点模块中。修改全局库函数非常糟糕。

#2


1  

I didnt get clear scenario of your problem,but as your question title ,we can execute the any cmd command from nodejs using npm libraries like:

我没有弄清楚你的问题,但是作为你的问题标题,我们可以使用npm库执行nodejs的任何cmd命令,比如:

import Promise from 'bluebird'
import cmd from 'node-cmd'

const getAsync = Promise.promisify(cmd.get, { multiArgs: true, context: cmd })

getAsync('node -v').then(data => {
  console.log('cmd data', data)
}).catch(err => {
  console.log('cmd err', err)
})  

#3


0  

Everything passed in the command line AFTER your nodejs application is parsed into an array called process.argv. So...

在nodejs应用程序被解析为process.argv数组之后,命令行中传递的所有内容。所以…

node myapp.js foo bar hello 5000

In your nodejs code...

在nodejs代码…

const args = process.argv;
console.log(args[0]);
console.log(args[1]);
console.log(args[2]);
console.log(args[3]);

would yield...

将产生……

foo
bar
hello
5000