关于node.js:如何将命令行参数传递给从可执行脚本启动的NodeJS

How to pass command line arguments to NodeJS launched from an executable script

对于从启动程序脚本运行的NodeJS进程,如何将命令行参数设置为node? (sh / CMD脚本npm放入node_modules/.bin。)

大量的NodeJS库/框架都带有自己的运行脚本,例如通常从npm脚本执行的zeit/micromoleculer。这在开发中带来了一个问题,因为就我而言,我想做以下事情:

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

(当然,除了index.ts只是导出一些内容供跑步者使用外,它什么也不做。)

是否有某些我不希望这样做的"干净"方式,最好是通用方式(即不特定于给定框架的运行器公开那些命令行参数),而理想情况下,它可以用作npm脚本?似乎可行的唯一方法是例如micro

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

冗余部的冗余部有点令人mouth舌,并且似乎消除了拥有这些启动器脚本的意义。 (如果运行程序生成其他Node进程,它也将不起作用,但这并不是我真正遇到的问题。)我不想不必重复启动程序脚本已经在做的事情。我也知道npx具有--node-arg,但是npx是另一种蠕虫。 (在Windows上,启动时间只有五秒钟,并且只是一个虚假的错误消息,用于运行我已经安装的脚本;如果找不到.cmd启动器脚本,例如,当使用Docker运行开发环境。总之,我宁愿不使用npx。)

为了消除似乎在注释中出现的混乱:我想覆盖影响NodeJS运行时自身执行运行脚本的行为的命令行参数,而不是将参数传递给脚本本身或我的代码。即,此处列出的选项:https://nodejs.org/api/cli.html


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

所以这里的示例是能够做到的

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

但实际上并没有写出来,而是让wrap.js注入了args:

node ./wrap.js ./test.js

我测试了通过package.json中的npm运行它,它工作正常。我通过让some-module.js在全局对象上粘贴一个值,然后将其记录在test.js中来测试它是否正常工作。

涉及的文件:

wrap.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);

some-module.js

1
global.testval = 2;

test.js

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

编辑:因此,经过进一步思考,此解决方案实际上仅满足包??装初始跑步者的要求。但是大多数工具(例如,摩卡咖啡)会重新生成一个子进程,这将失去这种效果。要真正完成这项工作,您可以代理每个子流程调用,并在某种程度上强制执行对spawn的调用,其中还包括您的参数。

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

package.json

1
2
3
4
5
6
7
8
{
 "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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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

1
2
3
4
5
6
7
8
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();
  })
})

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


我没有清楚了解您的问题的情况,但是作为您的问题标题,我们可以使用npm库从nodejs执行任何cmd命令,例如:

1
2
3
4
5
6
7
8
9
10
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)
})


在您的nodejs应用程序之后,在命令行中传递的所有内容都将解析为一个名为process.argv的数组。所以...

1
node myapp.js foo bar hello 5000

在您的nodejs代码中...

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

会产生...

1
2
3
4
foo
bar
hello
5000