0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

npmの仕組みを整理して、パッケージ登録・管理まで理解する

0
Posted at

はじめに

npmについて、これまでは主に npm install を実行する道具として使っていた。しかし、パッケージを自分で登録・公開・管理する立場になると、単なるインストーラではなく、CLI、registry、package.json、lockfile、権限管理、バージョン管理を含むパッケージ管理基盤として理解する必要がある。

この記事では、npmの仕組みを自分なりに整理し、最終的に自社または個人のパッケージを登録・管理できる状態を目指す。


npmは何をしているのか

npmは、大きく次の3つで構成される。

要素 役割
npm CLI npm install, npm publish, npm version などを実行するローカルツール
npm registry パッケージ本体とメタデータを保存するサーバー
package.json / package-lock.json パッケージ定義と依存関係を管理するファイル

npm CLIは、パッケージ名とバージョンをもとにregistryへ問い合わせ、必要なパッケージを取得する。デフォルトではnpm public registryが使われる。registryはパッケージ名とバージョンを解決するためのデータベースのような存在で、npm CLIはそこからtarballとメタデータを取得する。1

つまり、npmを使うということは、単にファイルをダウンロードしているのではなく、名前、バージョン、依存関係、公開範囲、権限を含むエコシステムを操作しているということになる。


packageとmoduleの違い

npmにおけるpackageは、基本的には package.json によって定義されたディレクトリまたはtarballである。npm registryへ公開するには package.json が必要になる。一方、moduleはNode.jsの require()import で読み込める単位であり、すべてのmoduleがnpm packageとは限らない。2

ざっくり次のように考えるとよい。

package = npmで配布・管理される単位
module  = Node.jsから読み込まれる実行単位

多くの場合、この2つは重なるが、概念としては分けておいた方がよい。


package.jsonはパッケージの契約書

npm packageの中心は package.json である。

最低限重要なのは nameversion だ。npm公式ドキュメントでも、package.json には nameversion が必要であり、name は小文字・空白なし、version はsemantic versioningに従う形式とされている。3

例として、TypeScriptでライブラリを作る場合は次のような構成になる。

{
  "name": "@your-org/example-lib",
  "version": "0.1.0",
  "description": "Example internal library",
  "type": "module",
  "exports": {
    ".": "./dist/index.js"
  },
  "types": "./dist/index.d.ts",
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "scripts": {
    "build": "tsc",
    "test": "node --test",
    "prepublishOnly": "npm run build && npm test"
  },
  "license": "MIT"
}

ここで重要なのは files である。files を指定すると、公開対象を明示できる。npmは npm pack --dry-run で公開対象ファイルを確認できる。package.json, README.md, LICENSE などは原則として含まれ、files を指定した場合は指定対象が中心になる。4

パッケージ公開前には必ず次を実行するべきである。

npm pack --dry-run

これは、秘密情報や不要ファイルを誤ってnpm registryに上げないための最低限の確認になる。


npm installの仕組み

npm install は、単に依存ライブラリをダウンロードするだけではない。package.jsonpackage-lock.json を見て、依存関係ツリーを解決し、node_modules を構築する。

package-lock.json がある場合、npm install はそのlockfileを使って依存関係を解決する。lockfileの解決済みバージョンが package.json の範囲を満たしていればlockfileが優先され、範囲と合わなければ再解決してlockfileが更新される。5

整理すると次のようになる。

package.json      = 許容する依存バージョンの範囲
package-lock.json = 実際に解決された依存バージョンの固定結果
node_modules      = 実際に展開された依存パッケージ群

package-lock.json は、生成された依存ツリーを記録し、後続のinstallで同じ依存関係を再現するためのファイルである。通常はリポジトリにコミットする。6

CIや本番ビルドでは、基本的には npm install より npm ci を使う方がよい。npm ci は既存の package-lock.json を前提にし、package.json とlockfileが一致しなければエラーにする。また、既存の node_modules を削除してクリーンインストールする。7

npm ci

つまり、開発中は npm install、CIでは npm ci という使い分けが自然だ。


scopeを使って名前空間を管理する

npm packageには、unscoped packageとscoped packageがある。

種類 説明
unscoped lodash グローバルな名前空間に置かれる
scoped @your-org/logger userまたはorganizationの名前空間に属する

scopeは @scope/package-name という形式を取る。scopeは関連パッケージをグルーピングする仕組みであり、パッケージ名の扱いにも影響する。8

会社や組織で管理するなら、基本的には次のようにするのがよい。

@your-org/logger
@your-org/config
@your-org/http-client
@your-org/cli-tools

これにより、名前衝突を避けつつ、organization単位で権限管理できる。

organization scopeにpublic packageを公開するには、そのscope名に対応するnpm organizationが必要である。また、scoped packageは初回公開時にpublic扱いにしたい場合、npm publish --access public を指定する必要がある。9


public packageとprivate package

npm packageにはpublicとprivateがある。

unscoped packageは基本的にpublicである。一方、private packageはuser-scopedまたはorganization-scoped packageとして公開する。private packageを使うには、有料のnpm user accountまたは有料organizationが必要になる。10

整理すると次のようになる。

種類 公開範囲
unscoped public example-lib public
scoped public @your-org/example-lib public。ただし初回publish時に --access public
scoped private @your-org/internal-lib 権限を持つuser/teamのみ

public scoped packageの作成例は次の通り。

mkdir example-lib
cd example-lib
npm init --scope=@your-org
npm publish --access public

private scoped packageの場合は次のようになる。

mkdir internal-lib
cd internal-lib
npm init --scope=@your-org
npm publish

private packageは便利だが、会社運用では料金、権限、CI/CD、退職者アカウント、token管理まで含めて設計する必要がある。


npm publishは「名前とversionをregistryに登録する操作」

npm publish は、カレントディレクトリまたは指定したpackageをregistryへ公開し、名前でinstallできるようにするコマンドである。4

基本形は次の通り。

npm publish

scoped public packageの場合は次の通り。

npm publish --access public

ここで特に重要なのは、一度公開した name@version は再利用できないという点である。同じ名前とバージョンの組み合わせは、unpublishした後でも再利用できない。4

つまり、次のような運用はできない。

@your-org/example-lib@1.0.0 をpublish
不具合に気づいてunpublish
同じ @your-org/example-lib@1.0.0 を修正して再publish

この場合は、次のバージョンを切る必要がある。

npm version patch
npm publish

npm publishは「アップロード」ではなく、immutableなリリースをregistryに刻む操作と考えるべきである。


バージョン管理はsemantic versioningで考える

npmではsemantic versioning、つまり MAJOR.MINOR.PATCH の考え方が基本になる。後方互換のバグ修正はpatch、後方互換の新機能はminor、後方互換を壊す変更はmajorを上げる。11

変更内容 上げる場所
後方互換のバグ修正 patch 1.0.01.0.1
後方互換の機能追加 minor 1.0.01.1.0
破壊的変更 major 1.0.02.0.0

npmには npm version コマンドがあり、package.json やlockfileのversionを書き換えられる。patch, minor, major, prerelease などを指定できる。12

npm version patch
npm version minor
npm version major

通常のリリースフローは次のようになる。

npm test
npm run build
npm version patch
npm publish

Git tagも含めて管理する場合は、npm version の挙動を理解した上で使う必要がある。


dist-tagでlatest以外のリリースラインを管理する

npmにはdist-tagという仕組みがある。これは、特定のversionに latest, beta, next, canary のようなラベルを付ける仕組みである。

npm publish は、特に指定しなければ公開したversionに latest tagを付ける。npm install <pkg> は、versionやtagを明示しない場合、通常 latest tagをinstallする。--tag を使えば、たとえばbeta版として公開できる。13

beta版を公開する例。

npm version prerelease --preid=beta
npm publish --tag beta

利用側は次のようにinstallする。

npm install @your-org/example-lib@beta

安定版に昇格させる場合は、dist-tagを付け替える。

npm dist-tag add @your-org/example-lib@1.2.0 latest
npm dist-tag ls @your-org/example-lib

dist-tagは「バージョン番号」とは別に「利用者にどのリリースラインを見せるか」を制御する仕組みである。


organizationと権限管理

会社でnpm packageを管理するなら、個人アカウント配下ではなくnpm organizationを使うべきだと考える。

npm organizationにはOwner、Admin、Memberの3つのroleがある。Ownerはメンバーやbillingを管理し、Adminはteam membershipやpackage accessを管理し、Memberはorganization scope内でpackageを作成・公開できる。14

会社運用では、たとえば次のような設計が考えられる。

organization: your-org
scope: @your-org

owners:
  - 技術責任者
  - 経営側の管理者

teams:
  - maintainers: read-write
  - developers: read-only
  - ci-publishers: publish用

team単位でpackage accessを付与する場合は、CLIでも設定できる。

npm access grant read-only your-org:developers @your-org/example-lib
npm access grant read-write your-org:maintainers @your-org/example-lib

organization ownerまたはpackage maintainerは、npm access grant <read-only|read-write> <org:team> [<package>] でteam accessを追加できる。15

個人アカウントにpublish権限を直接持たせすぎると、退職・異動・アカウント侵害時のリスクが大きい。そのため、会社運用ではorganization、team、CI/CDを前提にした方がよい。


2FAとtoken管理

npm packageの公開・設定変更にはセキュリティ設計が必要になる。

packageの作成・公開には2FA、または2FA bypassを有効にしたgranular access tokenが必要であり、package設定変更にも2FAが必要である。CI/CDではTrusted Publishingの利用が推奨されている。16

npm access tokenはgranular access tokenを中心に設計されており、対象package/scope、organization、期限、IP制限、read-only/read-write、2FA bypassなどを制御できる。17

手動publishをするなら、最低限次を守るべきである。

- maintainerは2FA必須
- publish権限を持つ人数を最小化
- tokenはgranular access tokenを使う
- 長寿命tokenを雑にCIへ置かない
- publish前に npm pack --dry-run を実行

CI/CDではTrusted Publishingを優先する

CI/CDからnpm packageを公開する場合、長寿命のnpm tokenをCI secretsに置く構成は避けたい。

npmのTrusted Publishingは、OIDCを使ってCI/CD workflowから直接publishできる仕組みであり、長寿命npm tokenを不要にする。GitHub Actions、GitLab CI/CD、CircleCI cloudなどがサポート対象として挙げられている。18

GitHub Actionsでの運用イメージは次のようになる。

Git tagをpush
  ↓
GitHub Actionsがtest/buildを実行
  ↓
npm Trusted Publishingでpublish
  ↓
npm registryにpackageが登録される

会社運用では、手元のPCから npm publish するより、release workflowを整備してCI/CDからpublishする方が再現性・監査性・セキュリティの面でよい。


unpublishよりdeprecateを優先する

npm packageは、一度公開したら基本的には消さない前提で考えるべきだ。

npmのUnpublish Policyでは、registry dataはimmutableであり、一度公開されたpackageは変更できないと説明されている。また、同じ package@version はunpublish後も再利用できない。unpublish条件を満たさない場合は、packageをdeprecateして、利用者のbuildを壊さず警告を出すことが推奨されている。19

deprecateの例。

npm deprecate @your-org/example-lib "Deprecated. Use @your-org/new-lib instead."
npm deprecate @your-org/example-lib@1.2.3 "Security issue. Upgrade to 1.2.4."

npmにおける削除は最終手段である。通常は次の順で対応する。

1. 修正版を新しいversionでpublish
2. 問題のあるversionをdeprecate
3. どうしても必要な場合だけunpublishを検討

最初に試すべき流れ

実際に理解するには、検証用packageを作ってpublishまで試すのが早い。

# npmにログイン
npm login
npm whoami

# package作成
mkdir example-lib
cd example-lib
npm init --scope=@your-org

# 実装
mkdir src
echo 'export const hello = () => "hello";' > src/index.js

# 公開対象確認
npm pack --dry-run

# public scoped packageとして公開
npm publish --access public

公開後、別プロジェクトからinstallして確認する。

npm install @your-org/example-lib

private packageとして運用する場合は、organizationの課金状態、team access、CI/CD、tokenまたはTrusted Publishingを先に整理する。


npm運用の要点

npmは単なるinstallツールではなく、JavaScript/TypeScriptのパッケージ流通基盤である。

特に重要なのは次の点だ。

- package.json はパッケージの定義ファイル
- package-lock.json は依存解決結果の固定ファイル
- npm registry は package@version を保存する場所
- npm publish は immutable なリリース操作
- 一度使った name@version は再利用できない
- 会社運用では @org/package のscope設計が重要
- public/private、team access、2FA、token、CI/CDまで含めて設計する
- 削除よりdeprecateを優先する

最終的には、npm packageを登録・管理するという作業は、単にコードを公開することではない。名前空間、依存関係、バージョン、公開範囲、権限、リリースプロセスを管理することだ。

まずは検証用のscoped packageを作り、npm pack --dry-runnpm publish --access publicnpm install @scope/package まで一通り試すと、npmの全体像がかなり明確になる。


参考文献

  1. Registry | npm Docs
  2. About packages and modules
  3. Creating a package.json file
  4. npm-publish | npm Docs
  5. npm-install | npm Docs
  6. package-lock.json | npm Docs
  7. npm-ci | npm Docs
  8. Scope | npm Docs
  9. Scope | npm Docs
  10. Creating and publishing private packages | npm Docs
  11. About semantic versioning | npm Docs
  12. npm-version | npm Docs
  13. npm-dist-tag | npm Docs
  14. Organization roles and permissions | npm Docs
  15. Managing team access to organization packages | npm Docs
  16. Requiring 2FA for package publishing and settings modification | npm Docs
  17. About access tokens | npm Docs
  18. Trusted publishing for npm packages | npm Docs
  19. npm Unpublish Policy | npm Docs

補足として、[8][9] は同じnpm Scopeドキュメントです。記事としては片方に統一しても問題ありません。公開用には、?utm_source=chatgpt.com のようなトラッキングパラメータは削除するのが適切です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?