`npm token create --json`をchild_processで実行しつつ、結果のトークンはコンソールに表示しない方法

`npm token create`をNodeJSのプログラム中から呼びたくて、


execだとプロセスが終わるまで帰ってこないので、対話的なプログラムには向かない

```javascript:帰ってこない
const exec = child.exec("npm token create --json", (err, stdout, stderr) => {
	console.info({ err, stdout, stderr })
})
```

非同期処理を書きたくないのでspawnSyncを使おうとした。

やりたいことは

1. パスワードの入力を求めるメッセージはコンソールに表示したい
2. ユーザーが入力するパスワードはコンソールに表示したくない
3. 最終結果は（秘密のトークンを含むので）コンソールに表示したくない

である。
とりあえず

```javascript
const child = require('child_process')
const options = {
	encoding: "utf-8",
	stdio: "inherit"
}
const result = child.spawnSync("npm", ["token", "create", "--json"], options);
console.info("result", result)
```

こうすると、1と2はクリアできるけど3がだめ。結果が普通に表示される。（そりゃそうだ）

じゃあこういうことでしょ？


```javascript
const child = require('child_process')
const { Writable } = require('stream')
class Hook extends Writable {
	_write(chunk, encoding, callback) {
		console.log("chunk", chunk.toString())
		callback()
	}
}
const hook = new Hook();
hook.fd = 1	// これがないとspawnに渡せない
const options = {
	encoding: "utf-8",
	stdio: ["inherit", hook, "inherit"]
}

const result = child.spawnSync("npm", ["token", "create", "--json"], options)
console.info("result", result)
```

と思ってやってみたが、挙動は変わらない。`hook._write`が呼ばれていない。
ちなみに`hook.fd`の値をいくつか変えて試してみたが、コンソールに表示されなくなったり`npm ERR! code EPIPE`が出たりする。

調べていると、spawnはカスタムストリームを受け取れない（バグ）という情報があった。
https://stackoverflow.com/questions/34967278/nodejs-child-process-spawn-custom-stdio
https://github.com/nodejs/node-v0.x-archive/issues/4030

これにならってこうすると、

```javascript
const child = require('child_process')
const result = {};
const { Writable } = require('stream')
class Hook extends Writable {
	_write(chunk, encoding, callback) {
		const str = chunk.toString()
		try {
			result = JSON.parse(str)
		}
		catch {
			console.log(str)
		}
		callback()
	}
}
const hook = new Hook();
const options = {
	stdio: ["inherit", "pipe", "inherit"]
}
const process = child.spawn("npm", ["token", "create", "--json"], options)
process.stdout.pipe(hook)
process.on("close", () => {
	console.info("result", result)
})
```

1と3はクリアできたが2がダメ。
1と3についても、渡ってきたデータが`JSON.parse`を通るかどうかで分岐させているのでどうもすっきりしない。

ここにあるような、`process.stdout.write`を一時的に上書きするアプローチもだめだった。
上書きしたwriteが呼ばれてなさそうな挙動。

https://stackoverflow.com/questions/26675055/nodejs-parse-process-stdout-to-a-variable
https://gist.github.com/pguillory/729616


結局、stdinもstdoutもpipeしてしまって、状況をひとつひとつハンドリングするこのようなコードになってしまった。
1も2も3もクリアできはしたけど、ぜんぜん納得いってない。

```javascript
const child = require('child_process');
// stdinとstdoutはユーザーコードで処理する。stderrは親プロセスに流す
const options = {
	stdio: ['pipe', 'pipe', 'inherit']
}
const proc = child.spawn("npm", ["token", "create", "--json"], options);
// デフォルトは`Buffer`（バイト列）なのでutf-8を指定
proc.stdout.setEncoding('utf-8')
// パスワード入力を受け付けるためにprompt-syncを使う
const prompt = require('prompt-sync')({ sigint: true })
let result = {};
proc.stdout.on('data', (data) => {
	try {
		// JSONでパースしてみる
		result = JSON.parse(data)
		// できたら結果が出たということなので子プロセスを終了してよい
		proc.kill()
	}
	// パースできなかったらこちらへ
	catch (e) {
		// パスワード入力を求められてたら
		if (data === "npm password: ") {
			// prompt.hideで入力受け付け
			const answer = prompt.hide(data)
			// 入力された文字列をproc.stdinに渡す
			// 改行コードを送らないと向こうで処理を始めてくれない
			proc.stdin.write(answer + "\n")
		}
	}
});
proc.stdout.on('end', () => {
	console.info("result", result)
});
```


```
node main.js

npm password: 
result {
  token: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  cidr_whitelist: [],
  readonly: false,
  created: '2020-08-14T06:03:21.551Z'
}
```
---

そういえば、stdoutをpipeしているのに、`npm password: `がコンソールに表示されるのはなんでだ・・・？

https://github.com/npm/cli/blob/latest/lib/token.js#L210
https://github.com/npm/cli/blob/latest/lib/utils/read-user-info.js#L41
https://github.com/npm/read/blob/master/lib/read.js#L19

こう読み進めていくと、結局`process.stdout`を使っていそうなのだが、`child_process`内での`process.stdout`への出力がpipeされるのではないのか・・・？

ちょっとよくわからないので放置しとく・・・。