GitLabのパッケージレジストリにnpmパッケージを公開する

以前、Tailwind CSSを使ったコンポーネントの設定を複数のプロジェクトで使いまわす方法を調べた。

GitリポジトリとしてGitLab.comを使っており、GitLabのパッケージレジストリにnpmパッケージを公開することで、公開範囲を絞ることができるため、その方法をメモ。

環境

Node.js v16.15.1, npm v8.11.0, Yarn v1.22.19。

OSはWindows 10 Pro 64bit 21H2、コマンドはGit 2.35.1.windows.2のGit Bashから実行して確認。

GitLabの設定

docs.gitlab.com

GitLab上でプライベートなプロジェクトを作成しておく。

パッケージレジストリの種類として、プロジェクトレベルとインスタンスレベルがある。今回はインスタンスレベルで設定する。

インスタンスレベルの場合、npmパッケージはスコープ付きにする必要がある。Gitプロジェクトが属するグループ名の先頭に @ を付与したものが、npmパッケージのスコープ名となる。

なお、実際のソースコードが書かれたGitプロジェクトと、npmパッケージを公開するGitプロジェクトは同じである必要はない。

パッケージ公開するNode.jsプロジェクトの設定

アクセストークンの発行

GitLab CI/CDによるパイプライン内ではなく、手動で実行する場合、パッケージレジストリとするGitプロジェクトにアクセス可能な、アクセストークンを発行する。アクセストークンのスコープは、 api のみでいい。

発行したトークンは、 .bashrc などで export NPM_TOKEN=... しておく。

package.json の変更

package.jsonname を、 <スコープ名>/任意のパッケージ名 に変更する。

GitLabのグループ名が my-org 、パッケージ名を components とする場合、 @my-org/components*1

また、 privatetrue であれば false にしておく。

.npmrc の変更

.npmrc に、以下の記述を追加。公開するGitLabプロジェクトのプロジェクトIDが必要となる。

<スコープ名>:registry=https://<GitLabドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/
//<GitLabドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/:_authToken=${NPM_TOKEN}

${NPM_TOKEN} には環境変数が設定される。GitLabドメインは、GitLab.comを使用している場合は gitlab.com 、オンプレミス環境であればそのドメインを設定する。

例として、GitLab.com上のGitプロジェクトで、グループ名が my-org , プロジェクトIDが 12345678 の場合、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/projects/12345678/packages/npm/
//gitlab.com/api/v4/projects/12345678/packages/npm/:_authToken=${NPM_TOKEN}
GitLab CI/CDのパイプライン経由で公開する場合

GitLab CI/CDのパイプライン経由でパッケージ公開を行う場合、CI/CD時の環境変数を利用するよう書き換えることも可能

@my-org:registry=https://gitlab.com/api/v4/projects/12345678/packages/npm/
//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}

レジストリURLは、 @${CI_PROJECT_NAMESPACE}:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/ のようにも書き換えられそうだが、ローカルでうっかり npm publish したときに www.npmjs.com に向きそうなので、固定値で設定している。

公開処理の実行

npm publish --dry-run し、 Publishing to ... で表示されるURLが、指定したレジストリURL(この例では https://gitlab.com/api/v4/projects/12345678/packages/npm/ )となっていれば正しく設定できている。

npm publish を実行したら、ブラウザで公開先のGitLabプロジェクトのページを開き、左フレームの「パッケージとレジストリ」>「パッケージレジストリ」を開く。

指定したパッケージ名およびバージョンで、npmパッケージが登録されていれば成功。

パッケージを利用するNode.jsプロジェクトの設定

アクセストークンの発行

パッケージレジストリとするGitプロジェクトにアクセス可能な、アクセストークンを発行する。アクセストークンのスコープは、 read_api のみでいい。

また、スコープ apiread_api を含むので、パッケージ公開するNode.jsプロジェクトの設定を行っているのであれば流用可能。

.npmrc の変更

.npmrc に、以下の記述を追加。公開側との違いとして、 /project/<プロジェクトID> が不要となっている。

<スコープ名>:registry=https://<GitLabドメイン>/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}

例として、GitLab.com上のGitプロジェクトで、グループ名が my-org の場合、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}

この設定が正しく行われていれば、普通に npm install できる。

Yarnを用いる場合

それぞれ npm publish および npm install で動作確認したが、プロジェクトのパッケージマネージャーとしてはYarn Classicを使っている。

それぞれ yarn publishyarn add に置き換えられないか確認した。

yarn publishによるパッケージ公開

結論から言うと、まったく使い物にならなかったので、 npm publish を使い続けることとした。

yarn publish はデフォルトでは対話型だが、 yarn publish -non-interactive で非対話型で実行できるものの、いろいろと問題があった。

  • パッケージに含めるファイルを調整したときに、確認のため npm pack --dry-run を使うが、 npm pack に該当するコマンドがYarnにない
  • package.jsonfiles で指定していないファイルがパッケージに含まれる

yarn publishの改善のissueもあるが、改善されそうもないので、 npm publish を使う。

yarn addによるパッケージ追加

yarn add を実行すると以下のエラーが発生する。

error An unexpected error occurred: "https://ドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/<スコープ名>/<パッケージ名>/-/<スコープ名>/<パッケージ名>-<バージョン>.tgz: Request failed \"404 Not Found\"".

同じ問題の質問が、GitLabフォーラムにあった。

forum.gitlab.com

コメントにあるように、 .npmrc//<GitLabドメイン>/api/v4/projects/:_authToken=${NPM_TOKEN} を追加すると成功するようになった。

先の例に追加すると、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}
//gitlab.com/api/v4/projects/:_authToken=${NPM_TOKEN}

振り返り

基本的には公式ドキュメント通りに進めるだけだったが、Yarnを使う場合にはもろもろ手直しが必要だった。

特にpublishの挙動の違いにはまいった、なぜうまく動かないのかもよくわからない。npm用の設定ファイルが読み込まれていないのか?

ただ、掘り下げるのも時間の無駄なので、調査はしていない。

あと、そろそろYarnもmodernに乗り換えないとかなぁ。

*1:グループ名は example にしようと思ったが、GitLab.com上に存在していた