LoginSignup
0
0

More than 3 years have passed since last update.

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

Posted at

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

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

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

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

やりたいことは

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

である。
とりあえず

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がだめ。結果が普通に表示される。(そりゃそうだ)

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

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

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

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もクリアできはしたけど、ぜんぜん納得いってない。

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されるのではないのか・・・?

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0