🍎

macOS で npm install -g 実行時にエラーが出るときの対処法

2023/03/31に公開

macOS で npm install -g 実行時にエラーが出るときの対処法について説明します。ここでは、
EACCES: permission denied, access '/usr/local/lib/node_modules/...' といったエラーが出る場合が対象です。このエラーへの対処法にはいくつか方法があります。ここでは、sudo コマンドを使って解決する方法と使わないで解決する方法を紹介します。

sudo コマンドを使う方法

sudo コマンドを使って解決する方法には2つ方法があります。

  • a) インストールするときに sudo コマンドを使う
  • b) ファイルのパーミッションを root からユーザーに変更する

インストールするときに sudo コマンドを使う

一般ユーザーで NPM 用のパッケージをグローバルにインストールしようとするからエラーになるので、これを解決すればインストールができるようになります。

ここで、一般ユーザーへ一時的に root 権限を付与する sudo コマンドというものがあります。このコマンドを使うと、一般ユーザーでも「root 権限が必要な NPM 用のパッケージをグローバルにインストールする」ということができるようになります。

これが一番素直な解決策でしょう。例えば、express-generator パッケージをインストールするとしたら下記のようにします。

sudo npm install -g express-generator

ファイルのパーミッションを root からユーザーに変更する

自分しか使わない macOS マシンの場合は、NPM 用のパッケージをグローバルにインストールするときに、わざわざ sudo コマンドを使わないといけないのは面倒です。

単純には、/usr/local/lib/node_modules に対するユーザーのファイルパーミッションが足りないことが原因なので、これをユーザー所有とすれば解決します。つまり、node 関連のファイルをユーザー所有とすれば良いので、その方法を説明します。

先に whoaminpm config get prefix の実行結果を確認しておきます。

$ whoami
user001
$ npm config get prefix
/usr/local/

コマンドの実行結果を別のコマンドに含めるには、コマンドを $() とで囲んで使います。whoaminpm config get prefix の実行結果を使いたい場合は、それぞれ $(whoami)$(npm config get prefix) とします。コマンドの実行結果である user001/usr/local/ をそのまま使ったほうがわかりやすく確実なのですが、環境依存の値を使う場合に、$(コマンド) を使ってコマンド実行例を示すことはよくあります。

少し話が長くなりました。以上のことを考慮すると、node 関連のファイルをユーザー所有とするには、一般的には下記のようなコマンドを実行するのが良いでしょう。

sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

sudo コマンドを使わない方法

sudo コマンドが使えない環境で対応したい場合もあるでしょう。ここでは、その方法について説明します。

Node.js の環境では、ツールのバージョン管理が煩雑になるので、個人的にはパッケージのグローバルインストールは推奨しません。ですから、自分は次のいずれかの方法を使うようにしています。

  • a) npm exec コマンド (npx コマンド) を使って実行する
  • b) 環境変数 NPM_CONFIG_PREFIX を設定してインストール先を変更して使う
  • c) プロジェクトの開発用依存パッケージとしてインストールして使う
  • d) 開発用ツールとして用意

npm exec コマンド (npx コマンド) を使って実行

npm exec コマンドを使うと、指定したコマンドを含むパッケージがユーザーのホームディレクトリーにある ~/.npm/_npx にダウンロードされて使えるようになります。npm exec コマンドの短縮形が npx コマンドなので、そちらを使っても良いです。

npm exec コマンドと npx コマンドでのコマンド実行方法は npm のバージョンによって微妙に違ったりするので、自分が使っているものでの使い方は --help で確認して使うようにしましょう。

例えば、手元では次のように npm コマンドのヘルプを表示させると npm exec コマンドのヘルプが表示されます。

$ npx --help
Run a command from a local or remote npm package

Usage:
npm exec -- <pkg>[@<version>] [args...]
(略)

これを使うと、例えば express-generator コマンドを使う場合は次のようにコマンドを実行します。初めて実行するときはインストールして良いか確認する Ok to proceed? (y) のプロンプトが表示されます。y を入力すると、インストールされます。

$ npm exec -- express-generator --help
Need to install the following packages:
  express-generator@4.16.1
Ok to proceed? (y) y
(略)
  Usage: express [options] [dir]
(略)

この後も、コマンドを実行するには、npm exec -- をつけて実行する必要がありますが、グローバル用のディレクトリーを使わずにコマンド実行ができます。

npm exec コマンドで実行するコマンドのパッケージは一時的なキャッシュなので、クリアすることができます。キャッシュクリアには clear-npx-cache コマンドを使うのが良いでしょう。

$ npm exec -- clear-npx-cache
Need to install the following packages:
  clear-npx-cache@1.0.1
Ok to proceed? (y) y

環境変数 NPM_CONFIG_PREFIX を設定

環境変数 NPM_CONFIG_PREFIX を使うことで、npm コマンドが使うディレクトリーを変更することができます。たとえば、${HOME}/.npm-global~/.npm-global と同じ)を作成して、ここを使うようにすれば、ファイルパーミッションの問題は解決します。

具体的な手順について説明します。

最初に NPM_CONFIG_PREFIX へ指定するディレクトリーの用意をします。すでにインストールされているものを使えるようにするため、cp コマンドでそれらのコピーもします。

mkdir -p ${HOME}/.npm-global/lib
cp -r $(npm config get prefix)/lib/node_modules ${HOME}/.npm-global/lib/

環境変数 NPM_CONFIG_PREFIXPATH を設定してから npm install -g コマンドを実行してパッケージをインストールします。次の例では、express-generator パッケージをインストールしています。

$ export NPM_CONFIG_PREFIX=${HOME}/.npm-global
$ export PATH=${NPM_CONFIG_PREFIX}/bin:$PATH
$ npm install -g express-generator

これで、express-generator コマンドが使えるようになります。

次に念の為に npm config コマンドで NPM_CONFIG_PREFIX 環境変数と同じディレクトリーを prefix に指定しておきます。

npm config set prefix '${HOME}/.npm-global'

これを実行すると、${HOME}/.npmrc ファイルに設定が保存されます。

$ cat ${HOME}/.npmrc 
prefix=/home/node/${HOME}/.npm-global

もしデフォルト値に戻したい場合は prefix の行を消します。この行しかないなら、${HOME}/.npmrc ファイルを削除しても良いです。

さて、ここで、環境変数を毎回指定するのは大変です。bash を使っている場合は ${HOME}/.profile${HOME}/.bash_profile に指定しておくと良いでしょう。

export NPM_CONFIG_PREFIX=${HOME}/.npm-global
export PATH=${NPM_CONFIG_PREFIX}/bin:$PATH

zsh を使っている場合は、${HOME}/.zprofile${HOME}/.zshrc へ同じ内容で指定しておくと良いでしょう。

これらの環境変数を ${HOME}/.profile などへ追加したら、一度ログアウトしてから再ログインします。すると、指定が反映されます。

プロジェクトの開発用依存パッケージとしてインストール

プロジェクトの開発用依存パッケージとしてインストールすると、プロジェクト用の node_modules ディレクトリーにインストールされるので、グローバル用のディレクトリーで発生するパーミッションの問題は、そもそも起きません。

そのプロジェクトで使うツールのバージョンも固定して継続して使いやすくなります。プロジェクトが変わると同じバージョンのものであっても別途インストールすることになるので、その点がグローバルにインストールするのよりも不利になります。

プロジェクト用ディレクトリーの project001 があったとして、express-generator をプロジェクトの開発用依存パッケージとしてインストールして使うには、次のようにします。

$ cd project001
$ npm install -D express-generator
$ ./node_modules/.bin/express-generator --help

project001/node_modules/.bin ディレクトリーにあるコマンドは、このようにして実行できます。

カレントディレクトリーを project001 としている場合は、npm execproject001/node_modules/.bin ディレクトリーにあるコマンドを実行することもできます。

$ cd project001
$ npx express-generator --help

  Usage: express [options] [dir]
 (略)

project001/package.json ファイルの scripts: にコマンドを指定して使えるようにすることもできます。

project001/package.json ファイルを編集して次のようにします。

{
  "scripts": {
    "express": "express"
  },
  "devDependencies": {
    "express-generator": "^4.16.1"
  }
}

express-generator パッケージのデフォルトで実行されるコマンドは express なので、それを指定しています。

利用するには、npm run コマンドを使います。コマンドへパラメーターを指定するには -- を間に入れます。

$ npm run express -- --help

> express
> express --help


  Usage: express [options] [dir]
(略)

開発用ツールとして用意

「プロジェクトの開発用依存パッケージとしてインストール」の場合の不利な点に「プロジェクトが変わると同じバージョンのものであっても別途インストールすることになるので、その点がグローバルにインストールするのよりも不利になる」というものがありました。これを改善する方法のひとつに、開発用ツールとして用意する方法があります。npm exec で実行するという方法で良い気がするのですが、知っておいて損はないので紹介しておきます。

例えば、npm のバージョンが 9 のときにグローバルへインストールして使いたくなったコマンドを ${HOME}/npm-tools/npm9 へ入れておくというルールで運用するとします。その場合は、次のようにすれば良いでしょう。

mkdir -p ${HOME}/npm-tools/npm9
cd ${HOME}/npm-tools/npm9
npm install express-generator

使うときは次のようにします。

$ ${HOME}/npm-tools/npm9/node_modules/.bin/express --help

> express
> express --help

  Usage: express [options] [dir]
(略)

こうしておけば、npm コマンドがバージョン9の間に作業する場合は、${HOME}/npm-tools/npm9 にインストールされているものを共通で使えば良いということになります。将来、npm コマンドがバージョン10になったら、${HOME}/npm-tools/npm10 を用意して、そちらを使うようにします。もし、バージョン10を普段使っている時に、バージョン9の頃に使っていたものを使いたい場面があったら、${HOME}/npm-tools/npm9 にインストールされているものを使えば良いということになります。

NPM やシェルの基本を知っていると当たり前のことなのですが、知らない人はわからないことが多いと思い、整理してみました。

Discussion