【Windows】npm run serve を Ctrl+C で終了する際の「バッチ ジョブを終了しますか (Y/N)?」を回避する

普通なら WSL を使うんですが、諸事情で Windows 側で node やら npm やらを動かす必要がありました。
ただ、開発サーバーを npm run serve で立ち上げて、Ctrl+C で終了しようとすると、「バッチ ジョブを終了しますか (Y/N)?」という謎のメッセージが。
色々試行錯誤してどうにか回避できる方法を編み出したので、ご紹介します。

Sponsored Link

導入方法

Windows で npm run serve を Ctrl+C で終了すると毎回「バッチ ジョブを終了しますか (Y/N)?」と表示されるのを回避するための PowerShell スクリプト
Windows で npm run serve を Ctrl+C で終了すると毎回「バッチ ジョブを終了しますか (Y/N)?」と表示されるのを回避するための PowerShell スクリプト - npm.ps1

上の Gist から npm.ps1npx.ps1 をダウンロードし、node.exenpm.cmd があるフォルダに移動します。
Node.js を普通にインストールした場合は C:\Program Files\nodejs でしょうか。私は C:\Applications\Node.js にインストールしているので、そこに移動しました。

これだけで導入は完了です。
PowerShell で npm run serve を実行した後、Ctrl+C で終了しても「バッチ ジョブを終了しますか (Y/N)?」が表示されなければ、うまく導入できています。

注意点として、PowerShell でのみ機能します。cmd.exe では機能しません。
そもそも cmd.exe はこれ以上良くなる事はないので、もし未だに cmd.exe を開発に使っているのなら PowerShell への移行を推奨します。
Windows 10 に同梱されている Windows PowerShell (PowerShell 5.1) もこれ以上アップデートされないようなので、私は現在も開発されている PowerShell 7 を使っています。

仕組み

Windows での npm コマンドの実体は、(node.exeのあるフォルダ)\node_modules\npm\bin\npm-cli.js です。
毎回フルパス打ってたらやってられないので、パスの通っている node.exe と同じフォルダに、呼び出し用に Linux (UNIX) では npm(シェルスクリプト)、Windows では npm.cmd(バッチファイル)が置かれていて、それが実行されます。
実行時に npm または npm.cmdnode "(node.exeのあるフォルダ)\node_modules\npm\bin\npm-cli.js" を実行することで、初めて出力が表示されるような仕組みになっているようです。

cmd.exe のカス仕様

ただし、cmd.exe の積年の仕様でバッチファイルには Ctrl+C で終了しようとすると、バッチの意図に関わらず「バッチ ジョブを終了しますか (Y/N)?」と訊かれ、Y を入力するかさらに Ctrl+C を入力しないと終了しない問題があります。
世界中の Windows ユーザーがこれに悩まされていますが、中の人いわく「cmd.exe は非常に壊れやすく、互換性の兼ね合いもありどうすることもできない」そう。最悪…。

つまり、npm の中でも npm run serve などの長時間実行したままにしておいて、終了したければ Ctrl+C を押すような機能では、終了時に必ず「バッチ ジョブを終了しますか (Y/N)?」と訊かれることになります。
これは cmd.exe の問題なので、npm.cmd がバッチファイルである以上、残念ながらどうすることもできません。

もう一度 Ctrl+C を押せばいいだけの話ではあるけど、Mac や Linux では一発なのに Windows だと毎回出てくるのが納得いかないし、あまりにも非人道的で人権が剥奪されていると言っても過言ではない。絶許。

同じ仕組みの npx も同様で、たとえば npx vue-cli-service serve を実行後に Ctrl+C すると同様に残念なことになります。

Python ではパスの通った場所に pip.exeyoutube-dl.exe のように Python スクリプトを呼び出すだけの exe ファイルを配置することでこの問題を回避しています。

PowerShell という選択肢

色々調べていると、vue-cli のコマンド (vue) には vue.cmd 以外にも vue.ps1(PowerShell スクリプト)が用意されていて、PowerShell で npm を実行しているならば vue.cmd の代わりに vue.ps1 が使われるようになっていることが判明。
PowerShell スクリプトなら当然「バッチ ジョブを終了しますか (Y/N)?」とかいう MS-DOS 時代の遺産は存在しないので、Ctrl+C したらその時点で終了してくれます。

npm の場合、Ctrl+C を押した時点で npm 自体は終了していて、Y を押しても N を押しても結局終了するので何も意味を成していない…。

vue-cli のように、npm や npx にも呼び出し用 .cmd の代わりとなる .ps1 を置いてあげれば、バッチファイルの代わりに PowerShell スクリプトが実行されるので、この問題を回避できるというわけです。
npm.ps1・npx.ps1 も vue-cli.ps1 がベースで、パスを npm と npx のものに書き換えたものになります。

他のコマンドでも「バッチ ジョブを終了しますか (Y/N)?」を回避する

npm install -g でインストールしたコマンドで他にも「バッチ ジョブを終了しますか (Y/N)?」が表示されてほしくないものがある場合も、同様の手法で回避できます。ここでは ESLint の例で説明します。

vue-cli や webpack-cli のように .cmd だけでなく .ps1 も同梱しているケースなら問題にはならないのですが、PowerShell だと Windows 標準ではスクリプトの実行が禁止されていたりするため(👈カス)、そのあたりでコケる事を想定してか、あるいは .cmd だけで良いと思っているのか、未だ .cmd だけしか用意されていないものもあります。
この手順はそうしたコマンドでも忌々しい「バッチ ジョブを終了しますか (Y/N)?」を回避するためのものです。

まず、通常の Node.js の構成であれば npm install -g でインストールしたパッケージは C:\Users\<UserName>\AppData\Roaming\npm にあるので、エクスプローラーでそのパスを開きます。
Windows における eslint コマンドは実際にはこのフォルダ内の eslint.cmd(バッチファイル)が呼びされているので、eslint.cmd の代わりに eslint.ps1 が呼びされれれば良いわけです。

次に、 npm.ps1eslint.cmd があるフォルダに eslint.ps1 としてコピーします。
そして、エディタで node_modules/npm/bin/npm-cli.js の部分を(2箇所あります)node_modules/eslint/bin/eslint.js のように、ESLint の実体の .js ファイルのパスに置き換えて保存します。

あとは PowerShell 上で eslint と実行して、実行途中で Ctrl+C で終了しても「バッチ ジョブを終了しますか (Y/N)?」が表示されなければ完了です。

コメント