この記事はドワンゴ Advent Calendar 2023の21日目の記事です。
フロントエンド開発をしている方は、何かしらnpm registryを利用していると思います。
特に業務などで内製のパッケージを作った場合はprivate registryに置いて利用することが多いはずです。
本記事は、private npm registryを別のところに移行する際の方法についてお話します。
背景
private npm registryとしてはnpmjs、Github Packages Registry、AWS Codeartifactといったクラウドのものから、Sinopia, Verdaccioなど自分でサーバを立てて運用するものなど様々なものがあります。
一つのレジストリをずっと使い続けられれば良いですが、何らかの事情により別のレジストリへ移行しなければならない状況が発生することもあります。
扱うリポジトリやパッケージが少なく、1, 2日で移行が完了する場合はnpmrcやregistry周りの設定をササっと書き換えれば済む可能性も高いですが、移行対象のパッケージ数が多く、かつパッケージ間の依存関係がある場合、日々の開発作業を止めずに移行するのは難しくなってきます。
そのような状況で単にnpmrcや周辺の設定を切り替えるだけでは済まない場合はどのように移行を進めれば良いのでしょうか。
単純な移行で問題となる点
単に1つずつレジストリ設定を書き換えていくとどのような問題が起こるか。
基本的に、1つのスコープで複数のレジストリを運用できない
例として以下のような依存構造を考えてみます。
@example/lib-a
, @example/lib-b
, @example/core
というパッケージがあり、lib-a
, lib-b
はcore
に依存していたとします。
この時、core
のレジストリが registry-2
に切り替わると、それ以降のバージョンはregistry-2
にのみpublishされるため、registry-1
を使っているlib-a
, lib-b
からは core
の最新版が取れず更新が滞ってしまいます。
パッケージ間の依存がなかったり、lib-a
, lib-b
のレジストリをすぐにregistry-2
に切り替えられる場合はあまり大きな問題になりにくいですが、長期にわたると必要な変更がすぐに反映できなかったり、マイグレーションコストも高くなってきます。
この問題は、スコープ毎にレジストリの設定を分けられる(≒同じスコープであれば同じレジストリを使うという前提の)仕様によって起こるものですが、スコープはパッケージ名で決まるのでパッケージ名の変更容易性が移行難易度に影響します。
上記例でいえば、core
のスコープを別のものにして、レジストリ設定も分けることができれば移行作業がシンプルになります。
スコープを変えずに移行する
スコープを変えることができる場合は、順次新しいスコープ、レジストリへ移行していき、参照する側も新しいものへ切り替えていけば時間はかかってもシンプルな手順で移行を進めることができます。
ではスコープを変更することができない場合を考えてみます。
前述の通り、単純に切り替えていってしまうと最新版の取得ができなくなるため、移行に時間がかかる見込みがある場合は移行前と移行後両方のレジストリにパッケージがある必要があります。
1つのパッケージを複数のレジストリへ配置
移行期間中は両方のレジストリへパッケージを配置するとした場合、それを実現する方法はいくつかあります。
ここでは3つ挙げますが、結論から言えば3つ目がオススメです。
- 移行後のレジストリのupstreamとして移行前のレジストリを指定する
- リリース時に両方のレジストリへpublish1する
- 片方のレジストリへpublishした後、そのパッケージのアーカイブをもう片方のレジストリへアップロードする
移行後のレジストリのupstreamとして移行前のレジストリを指定する
これはできるレジストリや条件が限られると思いますが、移行後のレジストリが存在しないパッケージはupstreamから取得するようにできれば、シンプルに移行が可能です。
リリース時に両方のレジストリへpublishする
1つ目のレジストリへpublishした後に、もう1つのレジストリへ再publishする方法です(便宜上、以後multi-publishと呼称します)。
両方のレジストリへ配置するとなった場合にすぐ思いつきそうな方法ですが、リポジトリの設定やリリースの仕方によって対応が異なる場合があります。
詳細は後述しますが、あまりオススメしません。
片方のレジストリへpublishした後、そのパッケージのアーカイブをもう片方のレジストリへアップロードする
3つのうちでは一番確実で難易度も低い方法に感じます。
具体的には、移行前のレジストリへpublishした後、そのレジストリからアーカイブをダウンロードし、移行後のレジストリへアップロードします(逆方向の同期でもOK)。
直接アーカイブをダウンロード、アップロードできないレジストリだと使えない方法ですが、大抵のレジストリは対応しているのではないかと思います。
この方法の場合、単一の仕組みで両方のレジストリへ配置できるので、publishやリリースの方法に左右されず作業コストが下がります。
なので個人的には1番オススメな方法です。
両方のレジストリへpublishする際の問題
先ほどオススメしない方法として、リリース時に両方のレジストリへpublishする方法を挙げましたが、これがなぜ避けた方がいいのかを具体的に書きます。
パッケージマネージャによって対応が異なる
npm, yarn, pnpmなどパッケージマネージャの種類によって使い方が異なるので、リポジトリによって使っているパッケージマネージャが異なると各々の対応が必要になります。
例えば、npmの場合は npm publish
した後に、別のレジストリへpublishするため npm publish --@example:registry=https://~
というコマンドを実行しますが、
yarnの場合はyarn publish
した後に環境変数 YARN_REGISTRY
で別のレジストリへ変えたり、configを変更してからyarn publish
し直す、というようにツールによって対応方法が変わってきます。
特にスコープ付きの場合、CLIでレジストリの向き先を簡単に変更できないことが多いので、.npmrc
等を書き換えてpublishするという複雑な対応が必要になります。
ちなみに、nrm や yrm というレジストリ切り替えツールもあるので使えるかもしれないです(私は未検証です)。
publish以外の操作も一緒にやるツールを使っていると対応が複雑になる
semantic-releaseやlernaなど、バージョニングの処理も含めてpublishするツールを使っている場合、2重にバージョンアップをしないようにする必要があり、lerna publish
した後にnpm publish
をするといった対応が必要になります2。
特に、yarn v3 + lernaの構成の場合、lernaはpublish時に("npmClient": "yarn"
の設定を入れていても.yarnrc.yml
ではなく).npmrc
を参照する3ので罠っぽい挙動にも繋がります。
リポジトリによって作法が異なると各対応が必要になる
上記のように、ツールの仕様に合わせた対応が必要になりますが、移行対象のすべてのリポジトリが同じツール、仕組みであれば対応方法も少なくて済みます。
しかし、リポジトリによってlernaを使っていたり、semantic-releaseを使っていたり、シンプルにnpmだけ使っていたり、というようにバラバラな仕組みだと各種対応が必要で非常に大変です。
こういった理由から複数のレジストリへpublishする仕組みを使うのは避けた方が良いでしょう。
どうしてもスコープを変えずにmulti-publishしないといけない!というあなたへ
ここまで読めばnpmレジストリ移行をしようとしている方はmulti-publishを選択するとは思えないですが、それでもこの方法をとらなければならない方もいるかもしれません。
そんな方のために、役に立つかもしれないTipsを載せておきます。
lernaの --registry
オプションは、スコープ付きのパッケージのレジストリ切り替えに使えない
--registry
はスコープ指定なしのレジストリを変更するオプションです。
したがって、.npmrc
に以下のような記述があったとすると、--registry
オプションで変更されるのは registry
のconfigであり、@example:registry
ではないため、適切にレジストリの向き先が変えられません。
registry=https://registry.npmjs.org/
@example:registry=https://example.com/npm/
//example.com/npm/:_authToken=~(以下略)
スコープ付きのレジストリ設定をCLIオプションで指定できないようなので、この場合は npm config set
などを使って.npmrc
を書き換えて対応します。
lerna使用時、プロジェクトディレクトリの.npmrc
を書き換えると uncommitted changes
のエラーでpublishできなくなってしまう
lernaはgitの差分があるとpublishに失敗するため、プロジェクトディレクトリの.npmrc
を書き換える方法でレジストリ変更をする場合、ダミーのコミットをしてからpublishするという対応が必要になります。
ignoreできるようにして欲しいという提案 も出ているようですが、対応される見込みはなさそうです。
yarn v3以降を使っている場合、.npmrc
をバージョン管理の対象にせず、プロジェクト内では.yarnrc.yml
のみを管理し、.npmrc
を変更してもlernaに怒られないようにするという回避策は可能です。
semantic-release使用時は、npm publish
でmulti-publishする
semantic-releaseはlernaほど複雑ではないので、1つ目のレジストリにsemantic-releaseでpublishした後、 npm publish --@scope:registry=https://~
などでmulti-publishが可能です。
さいごに
文字ばかりになってしまいましたが、private npm registryの移行を考えている方の一助になれば幸いです。
今回紹介した方法以外でも良い移行方法はあるかと思いますし、良いアイディアがある方はぜひコメント等残していっていただきたいです。
(意見や間違いの指摘も歓迎です)
それではまた。