記事を書こうと思ったきっかけ
自分は牛尾剛さんが書かれた「世界一流エンジニアの思考法」という本がお気に入りで特に今自分が実践したいと思っていることは、「理解に時間をかけること」と「レベル1、つまり基礎の基礎の知識を増やす」ということです。目先のタスクに囚われず上記のことに取り組むことで、長期的な目で見た生産性を高めることができ、より貢献できるエンジニアになれると考えています。
問題
make sam-startというコマンドをうち、サーバーレスアプリケーションをローカルで動かせるようにしようと試みたのですが、以下のようなエラーが発生しました。
cp: ./node_modules/prisma/libquery_engine-rhel-openssl-3.0.x.so.node: No such file or directory
Lambda関数を動かすためにprismaのクエリエンジンが必要で、node_module内からそれをコピーするコマンドをmake sam-start内に定義していました。しかし、いつもは問題なく動くこのコマンドですが、急にエラーが発生したのです。実際にエディタからnode_module内を参照してみてもlibquery_engine-rhel-openssl-3.0.x.so.nodeというディレクトリが存在しません。
仮説
この問題を解決するために以下のような仮説を立てました。
仮説: ユニットテスト実装のためにjestのパッケージをpnpm installで導入したが、その際にnode_moduleが再構築され、依存関係が壊れたことでクエリエンジンのパッケージのディレクトリが消えてしまったのではないか
仮説の根拠
・この2つの作業をする以前は今回エラーが起きたmake sam-startのコマンドは問題なく動作していたため
・仮説で行った操作はnode_moduleに直接的に影響を与えるコマンドであるため
仮説を確かめる検証を行うために基礎知識をつける
pnpmとは
Node.jsのパッケージマネージャでts/jsのプロジェクトの依存管理に多用される。
Pnpmで使用できるパッケージの実態ファイルはContent-addressable storeに保存され、node_module内のパッケージごとのファイルにはコンテンツストアへのハードリンクが記載されている。macだと~/Library/pnpm/store/v3がコンテンツストアの配置場所となっている。
pnpm installとは
プロジェクトの全ての依存関係をインストールするのに使用されます。package.json に記載された依存関係を node_modules にインストールします。この際、node_modulesはgitのブランチを変更しても内容は引き継がれます。
pnpm-lock.yamlとは
依存関係の正確なバージョンを記録するためのロックファイル。npmとは違いyaml形式です。コンテンツストアを使うという特徴から依存関係はフラットではなく、リンク構造で管理されます。
仮説の検証
まずはpnpm installを打った時のエラーログを確認します。
pnpm install typescript @types/jest jest ts-jest
WARN `node_modules` is present. Lockfile only installation will make it out-of-date
╭──────────────────────────────────────────╮
│ │
│ Update available! 10.7.0 → 10.13.1. │
│ Changelog: https://pnpm.io/v/10.13.1 │
│ To update, run: pnpm add -g pnpm │
│ │
╰──────────────────────────────────────────╯
/Users/miwatakuma/for-univearth/lifti-lambda/lambda/orderform-pdf:
ERR_PNPM_NO_MATCHING_VERSION No matching version found for prisma-error-enum@^1.0.0 while fetching it from https://registry.npmjs.org/
This error happened while installing a direct dependency of /Users/miwatakuma/for-univearth/lifti-lambda/lambda/orderform-pdf
The latest release of prisma-error-enum is "0.1.3".
If you need the full list of all 4 published versions run "$ pnpm view prisma-error-enum versions".
Progress: resolved 21, reused 0, downloaded 0, added 0
このようにjestのパッケージダウンロードの際にprisma-error-enumのバージョンが間違っていることによってエラーが起きています。また、実際にコミットログを見てみると写真のようにpackage.json,pnpm-lock.yamlが書き換わっていることが確認できます。
[pnpm-lock.yaml]
[package.json]
この結果を見るに、@prisma/clientとprismaのversionが6.3.1から6.3.1(typescript@5.8.3)へと変更されています。
ここで自分のミスに気づきました。pnpm-lock.yamlはgitで管理されており、dev,stage,prod全ての環境において共通で使用されるはずです。以前ajvのライブラリを追加した際も上司に問題ないか確認してもらっていました。つまり、prisma等の既存のライブラリのバージョンを勝手に変更することは間違っています。コミット前にしっかりと確認するべきでした。また、このことからおそらくpnpm installを再度実行すれば問題が解決されるのではないかと予想します。pnpmの公式docにも以下のように記載されていますね。
pnpm can automatically resolve merge conflicts in pnpm-lock.yaml. If you have conflicts, just run pnpm install and commit the changes.
しかし、自動的な競合の解消には注意が必要です。 コミットをステージングする前に変更内容を自分で確認することをお勧めします。pnpmが正しいバージョンを選択することは保証できないからです。ほとんどの場合、最新化したロックファイルでビルドすることで確認できます
では話を本題に戻します。次に調べるべきことは
typescript@5.8.3という依存関係はどのパッケージをインストールしたことが影響して追加されたのか
です。
上記の写真のコミットログや、以下のwarp(ターミナル)のコマンドからjestかprisma-error-enumの2つのパッケージのどちらかの追加が問題だと絞り込めます。
pnpm add -D jest ts-jest @types/jest
WARN `node_modules` is present. Lockfile only installation will make it out-of-date
WARN 2 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6
Progress: resolved 818, reused 0, downloaded 0, added 0, done
devDependencies:
+ @types/jest ^30.0.0
+ jest ^30.0.4
+ ts-jest ^29.4.0
Packages: +244 -1
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Done in 5s using pnpm v10.7.0
pnpm add prisma-error-enum
WARN `node_modules` is present. Lockfile only installation will make it out-of-date
WARN 2 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6
Progress: resolved 819, reused 0, downloaded 0, added 0, done
dependencies:
+ prisma-error-enum ^0.1.3
Already up to date
Done in 2s using pnpm v10.7.0
ではgithubの検索窓にtypescript@5.8.3を打ち込み、どちらのパッケージの依存関係か調べてみましょう。結果がこちらです。
ts-jest:
specifier: ^29.4.0
version: 29.4.0(@babel/core@7.28.0)(@jest/transform@30.0.4)(@jest/types@30.0.1)(babel-jest@30.0.4(@babel/core@7.28.0))(jest-util@30.0.2)(jest@30.0.4(@types/node@22.13.10))(typescript@5.8.3)
ビンゴ、ts-jestのパッケージが怪しそうですね。このことから以下の手順を踏むことで、同じエラーを再現できると考えます。
①別のブランチ(prismaのバージョンが以前と同じブランチ)で以下のコマンドを打つ
pnpm add -D jest ts-jest @types/jest
②pnpm-lock.yamlのprismaのバージョンが変更されることを確認する
結果、同じようにprisma,@prisma/clientにtypescript@5.8.3の依存関係が追加されました。この理由はjestがインストールされる際、pnpm-lock.yamlに明示的に書かれたtsを必要としますが、明示的に書かれていない場合は自動でtsを追加するからです。これによってprisma,@prisma/clientは別のパッケージと認識され、クエリエンジンが消えてしまったと考察します。
エラーの再現
再度行っても同じエラーが発生することを確かめるため、以下の手順でエラーを再現します。
①rm -r node_modulesで既存のnode_modulesを削除する
②pnpm installで再度node_modulesを構築
③Makefileに定義したmake prisma-generateでprismaクライアントやクエリエンジンを再構築
④make sam-startを実行し、問題なく動作することを確認する
⑤pnpm add -D jest ts-jest @types/jestを実行し、prisma,@prisma/clientのバージョンが変更されることを確認する。また、エディタのnode_modules/prismaディレクトリからクエリエンジンのファイルが消えていることを確認する。
⑥もう一度make sam-startを実行し、今回の問題となったエラー文が表示されていることを確認する
cp: ./node_modules/prisma/libquery_engine-rhel-openssl-3.0.x.so.node: No such file or directory
結果
③の段階で存在したクエリエンジンのファイルが⑤のコマンドをうつことによって消え、⑥の状態になることを確認できました。
エラーの原因
pnpm add -D jest ts-jest @types/jestを実行するによってクエリエンジンが消える理由はその後にmake prisma-generateコマンドを打っておらず、クエリエンジンが作られていないから
つまり、クエリエンジンが消えたのではなく依存関係を再構築したのちにクエリエンジンを作成していないだけ!
pnpmではバージョンが異なる同じパッケージは別パッケージとして扱われます。実際にnode_modules/.pnpmには新たなprisma@6.3.1_typescript@5.8.3用のディレクトリが構築されます。コマンドを打つことによって今後使用されるprisma@6.3.1_typescript@5.8.3バージョンのnode_modulesが構築されます。pnpm installコマンドだけを打ってmake prisma-generateは実行していない状態と同じと言えます。そのため、もちろんクエリエンジンは作成されません。これが、クエリエンジンがコマンドを打つことによって消えるように見えた原因です。実際に、make prisma-generateを実行すると写真のように問題なくnode_modules/prismaにクエリエンジンが構築されています。
学んだこと
普段使っているライブラリやパッケージがどのような仕組みで動いているのかを理解することで、苦手意識のあった依存関係の問題等に対しても対応できる。やはりどれだけレベル1の知識を増やせるか、基礎知識を持っているかが重要だと認識した。