.npmrcの使い方（GitHub Private Packages読み込み編）

## 概要
今回も備忘録です。

`.npmrc`ってなんのために使うんぞ？ってなっていたのですが、実際に業務で必要になる場面があったので、その時を思い出しながら書いてみます。

## 本記事でのゴール
タイトルの通りではありますが、<strong>`.npmrc`と呼ばれるファイルを作り、自分が所属している組織が所有し、かつ外部に公開されていないようなパッケージを利用できるように設定すること</strong>をゴールとします。

今回の記事では、<strong>GitHub Packagesに公開され、npmに関連付けられたパッケージ</strong>を対象とします。

こちらのパッケージ設定については、公式に[ドキュメント](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)がありましたので、興味があればご覧ください。


## .npmrcって？
<strong>npm（もしくはyarn）を利用する際の設定ファイル</strong>となります。
npmは、`.npmrc`に設定された設定や環境変数を読み込んで利用することが可能です。

（僕の主観でしかないですが）基本的には.npmrcは書かなくてもOKで、npmにデフォルトで設定されているコマンドや設定を利用すればOKだと思います。何かしら手を加えたくなった際に.npmrcで設定を上書きする感じですね。

今回の狙いはPrivate Packageを読み込む設定を行うことなので、詳細な設定などは省きます。気になる方は[こちら](https://docs.npmjs.com/cli/v8/using-npm/config)をご覧ください。

## Private Packageって？
文字通り、外部には公開されていないパッケージのことです。

例えば、

> 社内エンジニア「自社でテンプレートとして使うUIライブラリが完成したで！」
社内エンジニア「でも、外部には公開せずに関係者だけで使いたいなぁ...」

なんて場面で作られると思います。

自社のものでなかったとしても、
> お客様「これから一緒に開発よろしくやで！」
> お客様「ウチらのサービスは全部このUIライブラリ使ってるから」
> お客様「今回の開発もこのライブラリを使ってもらうやで」
> お客様「あ、Privateなパッケージだから認証設定しといてな！」

こんな場面もありそうですよね。

このようなときに、
> ぼく「パッケージがインストールできない！？」
> ぼく「パッケージが見つからないのだが...」

なんてことが起きないよう、認証の設定を行えるようにしておきましょう。

## 認証に必要なもの
認証には「<strong>GitHub Access Token</strong>」を使います。
Private RepositoryをCloneしたり、変更をPushする際に必要となって生成した方もいるかもしれません。今回も似たような感じです。

トークンの生成は[こちら](https://github.com/settings/tokens)から行ってください。

トークンの設定・有効期限はお好みですが、Private Packageの読み込みが目的なので、必ず`read:packages`にチェックを入れておきましょう。
![スクリーンショット 2022-09-17 13.07.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1692273/48337d8d-a2dc-6b45-d307-33388937b591.png)

## 認証のための`.npmrc`の書き方

下記のように記載します。

```:.npmrc
//npm.pkg.github.com/:_authToken=XXX
@OWNER:registry=https://npm.pkg.github.com
```
`authToken=XXX`には、GitHubで生成したアクセストークンを設定します。
`@OWNER`には、取得したいパッケージの所有者名（ユーザーもしくはOrganization）を設定します。

この部分の意味合いとしては、
> ぼく「この団体が登録しているパッケージが欲しいんすけど...」
> npm「このパッケージの所有元に所属しとるんならええで」
> ぼく「所属してます〜。これが認証用のアクセストークンです。」
> npm「拝見するやで。...ちゃんと所属しとるみたいやな！ヨシ！」

といった感じです。基本的にはOrganization名を入れる形になるかと。
この部分の設定が上手くできていれば、Privateなパッケージも使えるようになってるかと思います。

## ちょいと一工夫
パッケージの取得はできるようになりましたが、ちょっとした問題があります。

今回設定した`.npmrc`には、GitHubで作成したアクセストークンの情報が入っています。アクセストークンは、トークンの持ち主を認証し、権限を確認するための重要のものですので、<strong>そういった情報が`.npmrc`に存在する場合は、`.gitignore`で`.npmrc`を無視するようにして、情報をリポジトリ上に残さないようにするのがベター</strong>です。

こういった場合、チームでの開発となると、開発を行う人たち全員が.npmrcの設定を行う必要があり、やや面倒です。

そこで、`.env`ファイルに環境変数としてアクセストークンを設定し、パッケージを追加する際に自動的に`.npmrc`を生成できるように工夫してみます。

.envファイルは.npmrcと比べて知名度（？）も高いでしょうし、
> 先輩「認証必要だから、トークン作ってここに記載しといてや！」

という説明だけでOKなら、それに越したことはないと思います。

ファイルのパスなどは、ご自身の環境に合わせて修正していただければと思います。

### .npmrcテンプレートファイル
生成用のテンプレートファイルを作成しておきます。
`@OWNER`の部分は取得したいパッケージの所有者名に置き換えてください。
```.npmrc.sample
//npm.pkg.github.com/:_authToken=
@OWNER:registry=https://npm.pkg.github.com
```

### 環境変数設定
`.env`は必ず`.gitignore`に設定し、リポジトリ上に公開するためのサンプルファイルを代わりに作成しておきましょう。（アクセストークンをリポジトリ上に残さないため）
```.env
#github access tokenの設定
GITHUB_ACCESS_TOKEN=TESTTOKEN
```
```.env.sample
#github access tokenの設定
GITHUB_ACCESS_TOKEN=XXXXXX
```

### .npmrc生成用スクリプト
このスクリプトでは、
- `.env`に記載した環境変数の読み込み
- テンプレートファイルから`.npmrc`を生成
- 生成した`.npmrc`に環境変数の値を設定

を行なっています。
```:generate-npmrc.sh
source ./.env
\cp -r ./.npmrc.sample ./.npmrc
sed -ie "s/_authToken=/_authToken=$GITHUB_ACCESS_TOKEN/" ./.npmrc
```

### パッケージインストール時にスクリプトを実行
`package.json`に以下のスクリプトを追加します。
```
  "scripts": {
    "preinstall": "bash ./scripts/generate-npmrc.sh",
  },
```
npmスクリプトには、<strong>`pre`プレフィックスをコマンド前につけることで、特定のコマンド前に自動的に実行するコマンドを設定できる</strong>という、非常に便利な機能があります。
今回の設定例では、`npm install`を実行する直前に、先ほど作成した`.npmrc`作成用のスクリプトを自動的に実行してくれるようにしています。

ここまで設定すれば、パッケージを追加する際に自動的に`.npmrc`が生成されるようになっているかと思います。

今回の設定例では、以下のような`.npmrc`が生成されます。
```:.npmrc
//npm.pkg.github.com/:_authToken=TESTTOKEN
@OWNER:registry=https://npm.pkg.github.com
```

## おわりに
タイトルに〇〇編と書いてますが、続編の予定はございません。

以上です。👼

## 参考文献
- [npm公式 「config」](https://docs.npmjs.com/cli/v8/using-npm/config)
- [GitHub公式 「npmレジストリの操作」](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)