あの時の俺に言いたい。NPMにpublishしたら（ほぼ）消せないことを

この記事は[タイムリープTypeScript 〜TypeScript始めたてのあの頃に知っておきたかったこと〜](https://qiita.com/advent-calendar/2021/timeleap-typescript)
15日目の記事です。

みなさんはnpmにパッケージをpublishしたことがありますか？私はあります。
このNPM、実は一度publishしたパッケージはpublishしてから72時間以内でないと非常に削除がしづらくなります。一応削除するためには`npm unpublish`というコマンドがあるのですが、そのパッケージがpublishされてから72時間以内かどうかで対応が変わります。

[npm Unpublish Policy](https://docs.npmjs.com/policies/unpublish)
ここに説明があるのですが、72時間以内であれば
>For newly created packages, as long as no other packages in the npm Public Registry depend on your package, you can unpublish anytime within the first 72 hours after publishing.

つまり、npmの他の(publicである)パッケージが依存していない限り削除できます。一方72時間を超えると
>Regardless of how long ago a package was published, you can unpublish a package that:
- no other packages in the npm Public Registry depend on
- had less than 300 downloads over the last week
- has a single owner/maintainer

npmのpublicであるパッケージが依存しておらず、直近1週間のダウンロード数が300未満であり、オーナーまたはメンテナーが1人でなければ削除できません。

組織内で使う共通ライブラリやビジネスロジックなどを誤ってpublishしてしまうと迅速にunpublishしない限り非常に削除しづらくなってしまいます。

# 👶ではどうすればいいんですか
このような~~デジタルタトゥー~~をこれ以上生み出さないためにも今回紹介するのはnpmではなくGitHubのパッケージレジストリ、その名もGitHub Package Registry(以下GPRと称します)にパッケージをGitHub Actionsからpublishし、それを使用する方法を紹介いたします。

## GPRにpublishする

publishするためには大まかには以下の手順になります。

1. package.jsonの`name`に`@xxx`(スコープ)を追加する
1. package.jsonの`private`をfalseにする
1. package.jsonに`publishConfig`を書き込む

### package.jsonの`name`に`@xxx`(スコープ)を追加する

パッケージの名前を`yyy`から`@xxx/yyy`に変更します。一般的に`xxx`はパッケージのオーナーの名前を使い、常に`@`から始まります。

例: `"name": "awesome-package"` -> `"name": "@jamashita/awesome-package"`

### package.jsonの`private`をfalseにする

これはnpmにパッケージを登録するときと同じです。

例: `"private": true` -> `"private": false`

### package.jsonに`publishConfig`を書き込む

以下を追加します。

```json
"publishConfig": {
  "access": "restricted",
  "registry": "https://npm.pkg.github.com/"
}
```

`access`についてはpublicパッケージにしたければ`public`にし、privateパッケージにしたければ`restricted`に設定します。

あとはGitHub ActionsでGPRにpublishするjobを設定します。以下はそのサンプルです。

```yml
jobs:
  release:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16.0
          registry-url: https://npm.pkg.github.com
          scope: '@xxx'
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - run: npm install
      - run: npm publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

普通のpublishを行うときと異なるのは4点です。

### `actions/setup-node@v2`の`registry-url`

ここでpublishする向き先を`https://npm.pkg.github.com`に変更します。

### `actions/setup-node@v2`の`scope`

package.jsonで指定した`@xxx`(スコープ)を指定します。

### `actions/setup-node@v2`の`env.NODE_AUTH_TOKEN`

ここには`${{ secrets.GITHUB_TOKEN }}`を設定します。

### publishするときの`env: NPM_TOKEN`

`NPM_TOKEN`に`${{ secrets.GITHUB_TOKEN }}`を設定します。

これでGPRにGitHub Actionsを使ってパッケージをpublishできるようになりました。

## GPRのパッケージを取得する

GPRに登録されたパッケージを取得するには以下の手順が必要になります。

1. `read:packages`権限のPersonal access token(以下PATと称します)を取得する
1. `~/.npmrc`に先ほどのPATを書き込む
1. 使用したいプロジェクトの`.npmrc`にスコープの参照先を変更するよう記述する

### `read:packages`権限のPersonal access tokenを取得する

PATはユーザーのSettings > Developer settings > Personal access tokensで取得できます。このときの権限は上述のとおり`read:packages`のみです。

### `~/.npmrc`に先ほどのPATを書き込む

`~/.npmrc`に取得したPATを書き込みます。PATを`xxxxyyyyzzzz`とした場合、以下の一文を追加してください

```
//npm.pkg.github.com/:_authToken=xxxxyyyyzzzz                                                                                                         
```

### 使用したいプロジェクトの`.npmrc`にスコープの参照先を変更するよう記述する

使用したいプロジェクトのルートディレクトリに`.npmrc`を置き、以下の一文を加えます。

```
@xxx:registry = https://npm.pkg.github.com
```

`@xxx`はスコープです。こうすることによってこのスコープが付けられたパッケージだけはGPRにパッケージを探しに行くようになります。

これで通常どおり`npm install`でGPRに登録されたパッケージを使うことができるようになります。

## 登録したパッケージをGitHub Actionsで使いたい

CIなど、GitHub Actionsで登録したパッケージを使いたいときがあります。そのときは登録したパッケージがGPRにあることをGitHub Actionsに教えてあげないとたとえ使う側、使われる側が同じオーナーであってもパッケージをインストールすることができません。そこで以下のようにします。

### `read:packages`権限のPATを取得する

これは上述の**GPRのパッケージを取得する**の**`read:packages`権限のPersonal access tokenを取得する**と同じです。

### PATを使う側のプロジェクトのSecretsに追加する

**使う側の**プロジェクトにあるSettings > Secretsに先ほどのPATを追加します。

### GitHub Actionsで先ほど登録したPATを参照して`npm install`をする

追加したPATの名前が`GPR_TOKEN`ならGitHub Actionsのjobは以下のようになります。

```yml
jobs:
  ci:
    runs-on: 'ubuntu-latest'
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16.0
          registry-url: https://npm.pkg.github.com
          scope: '@xxx'
      - run: npm install
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GPR_TOKEN }}
      - run: npm test
```

特筆すべきところは1点です。

### `npm install`時に`env.NODE_AUTH_TOKEN`を設定する

ここで先ほど取得したPATの名前の前に`secrets.`を加えます。するとGitHub Actionsは`@xxx`のスコープのパッケージをGPRから取得できるようになります。

# 🤖個人だけではなく組織でも使いたい

組織の場合、個人のようにPATを取得することができません。ではGPRを使ったパッケージ管理ができないかというと**できます**。方法は簡単で組織に属しているメンバーの誰かがPATを取得し、同じようにすることでGitHub Actionsでパッケージの取得ができるようになります。
このときPATをプロジェクトのSecretsに加えても、組織のSecretsに加えても同様に機能します。

# 🧟注意すること

1. GPRにあるパッケージはNPMと異なり削除が容易です。誤って消してしまったり組織のメンバーの謀反に遭うとパッケージが消える恐れがあります
2. **できるだけ辞めなさそうな**組織のメンバーにPATを取得してもらってください