【pnpm】GitHub Actions や CircleCI で 扱ってる yarn を pnpm に置き換えた

とある事情により、複数のプロジェクトでパッケージ管理を yarn から pnpm に移行した。
CI ツールが GitHub Actions だったり、 CircleCI だったりしたので、それぞれどんな感じに書けるのかって話を久々の投稿としてアウトプットしておく。

## 共通してやること
yarn や npm から pnpm への移行を決めたらまず、 pnpm を取得する必要がある（当たり前
現時点での最新は pnpm v9 なので、以下よりお好きな方法で取得されたし。

https://pnpm.io/ja/installation

私は Homebrew を使用した。


```
brew install pnpm
```

各プロジェクトでパッケージ管理をどのようにしているかによるが、ここでは npm や yarn からの移行を想定しているので、各プロジェクトの lock ファイルがある場所で以下 cli コマンドを実行する。

https://pnpm.io/ja/cli/import

```
pnpm import
```
これで、新たに pnpm-lock.yaml を生成してくれるので、手元での移行はこれでおしまいなのでめちゃくちゃ簡単。不要になった旧パッケージ管理ツールの lock ファイルはこの場で削除しておく。

## GitHub Actions
CI のレシピも公式ドキュメントがあるので、基本的にはそのままパクれば良い。

https://pnpm.io/ja/continuous-integration#github-actions

### yarn

```yml
name: CI

on: [push]

env:
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18.x
      - run: yarn
      - run: yarn build
      - name: Upload elements
        uses: actions/upload-artifact@v3
        with:
          name: hoge-dist
          path: dist/hoge
```

### pnpm

```yml
name: CI

on: [push]

env:
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          package_json_file: package.json
      - uses: actions/setup-node@v4
        with:
          node-version-file: package.json
          cache: pnpm
      - run: pnpm install
      - run: pnpm build
      - name: Upload hoge
        uses: actions/upload-artifact@v3
        with:
          name: hoge-dist
          path: dist/hoge
```

pnpm へ置き換えるついでに、 Node のバージョンも package.json を参照するようにしたりすると便利かも。その辺のオプションは以下を参照

https://github.com/pnpm/action-setup

## CircleCI

### yarn

```yml
install_node_dependencies:
    steps:
      - restore_cache:
          name: Restore cache
          keys:
            - node-{{ arch }}-{{ checksum ".node-version" }}-{{ checksum "yarn.lock" }}
      - run:
          name: yarn install
          command: |
            yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
      - save_cache:
          name: Save npm cache
          key: node-{{ arch }}-{{ checksum ".node-version" }}-{{ checksum "yarn.lock" }}
          paths:
            - ~/.cache/yarn
```

### pnpm

```yml
install_node_dependencies:
    steps:
      - restore_cache:
          name: Restore pnpm Package Cache
          keys:
            - node-{{ arch }}-{{ checksum ".node-version" }}-{{ checksum "pnpm-lock.yaml" }}
      - run:
          name: Install pnpm package manager
          command: |
            sudo corepack enable
            sudo corepack prepare pnpm@latest-9 --activate
            pnpm config set store-dir ~/.cache/pnpm
      - run:
          name: pnpm install
          command: |
            pnpm install --frozen-lockfile
      - save_cache:
          name: Save pnpm package cache
          key: node-{{ arch }}-{{ checksum ".node-version" }}-{{ checksum "pnpm-lock.yaml" }}
          paths:
            - ~/.cache/pnpm
```

記述自体は簡単ですね。こんな感じで、ビルドとかテストしてるとこをよしなに書き換えれば良さそう。


## 雑記
まあ難しいのは yarn から移行する際、パッケージ管理が雑にされていると、アプリケーションが壊れることもあるので、リグレッションテストなどでの確認はしっかりしましょうという感じですかね。

あ、ここまで書いて触れてなかったですが今回の移行は yarn v1 からの移行です。yarn v1 は全ての依存パッケージがフラットに node_modules フォルダへ配置されます。

```
project
├── node_modules
│   ├── react
│   ├── react-dom
│   ├── lodash
│   ├── ...
```

一方で、 pnpm はプロジェクトの node_modules フォルダには依存関係のツリー構造が反映されます。各パッケージの依存関係はそのパッケージ自身の node_modules フォルダに配置されます。

```
project
├── node_modules
│   ├── .pnpm
│   │   ├── react@17.0.2
│   │   │   └── node_modules
│   │   │       ├── lodash@4.17.21
│   │   └── react-dom@17.0.2
│   │       └── node_modules
│   │           ├── react@17.0.2 (シンボリックリンク)
│   ├── react
│   ├── react-dom
│   ├── ...
```

.pnpm フォルダ内に依存パッケージが実際にインストールされ、 node_modules 内のパッケージはその .pnpm 内のパッケージを参照するシンボリックリンクとして存在します。

直接インストールされたパッケージのみに対しては、シンボリックリンクが作成されますが、それらの依存関係はそのパッケージ自身の node_modules に格納される為、ツリー構造が反映され、依存関係の隔離が実現されます。

で、これで何が起きるかというと、従来フラットにインストールされていたが、package.json に含めていなかったライブラリをプロダクションコード内でインポートしてたりするとコケます（当たり前

なんでそうなってんねんってことが起こるのも世の常なので、くれぐれもパッケージ管理ツール移行は慎重に。