🐻‍❄️

npm.jsでPrivate Packageを運用するあれこれ

2024/09/18に公開

この記事はスクラップ「npm.jsでPrivate Packageを運用する時のあれこれ」の内容を修正, 加筆したものです。

前提

全体的に1からPrivate Packageを作るハンズオン的な内容になっています。

条件

  • レジストリはnpm.jsを使用しています(よくあるGitHub Packagesではないので注意
    • npm.jsの有料契約($7/月/人)が必要です。
  • 諸々の取り扱いやすさからyarn(4系)をしています
  • CommonJSです。Native ESMにする方式は最後の方に書きます。(力尽きました

やりたいこと

$ npm install @ky-y./private-package

というように、組織内共通の「プライベートな」パッケージを、npm.jsレジストリで運用する。

結論

Private Package導入する側(アプリケーション側)はYarnのberryを用いると便利

NPM_TOKENの設定を美味しい感じに見たい場合は、#5, 使ってみるらへんをご参照ください。

基本手順

1, パッケージを作成する

$ mkdir private-library-0918 && cd private-library-0918
$ yarn init -2

厳密に言えば、パッケージ側はnpmでもYarn(Classic, Berry ...)でもなんでもいいんですが、せっかくなら導入する側と同じYarnの4系を使用します。

package.json
{
-  "name": "private-library-0918",
+  "name": "@ky-y./private-library-0918",
+  "version": "0.0.1",
+  "main": "./dist/index.js",
+  "types": "./dist/index.d.ts",
  "packageManager": "yarn@4.5.0"
}

初期では、スコープがついていないのでスコープをつけます。自分の場合は@ky-y.というスコープを所有しているので、そちらをつけます。
また、npmでパッケージを配信するにはバージョンが必須のため、初期バージョン0.0.1をつけます。
maintypesは、導入側から参照するファイルです。そのため、後述するdist内にビルドされたファイルを指定します。

.yarnrc.yml
yarnPath: .yarn/releases/yarn-4.5.0.cjs

+nodeLinker: node-modules

また、Yarnの設定ファイルである.yarnrc.ymlも初期ではnode_modulesを作成しない(Zero install)ようになっているので、node_modulesを作成するように設定を変更します。

2, TypeScriptの構築

$ yarn add -D typescript
$ npx tsc --init
tsconfig.json
{
    "compilerOptions": {
        "target": "ES2016",
        "module": "CommonJS",
        "outDir": "./dist",
        "esModuleInterop": true,
        "declaration": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    },
    "include": [
        "src/**/*.ts"
    ]
}

tsconfig.jsonの設定は上記のように変更しています。

3, 実装

あまり難しい実装はしません。

src/index.ts
export { Hoge } from "./hoge";
export { Piyo } from "./piyo";
src/hoge.ts
export class Hoge {
    readonly input: string;

    constructor(input: string) {
        this.input = input;
    }

    response = (): string => {

        return this.input;
    };

    output = (): void => {

        console.log(this.input);
    };
}
src/piyo.ts
import { Hoge } from "./hoge";

export class Piyo {
    readonly hoge: Hoge;

    constructor(hoge: Hoge) {
        this.hoge = hoge;
    }

    output = () => {

        this.hoge.output();
    };

    response = (): string => {

        return this.hoge.response();
    };
}

ここまで実行した時に、以下のコマンドを実行してください。

$ npx tsc

実行完了後、distディレクトリが以下のようになれば問題ないです。

4, 公開(限定公開)

$ npm publish --access=restricted

スコープがついたパッケージはデフォルトで限定公開になりますが、念のため明示的に指定します。
認証等が求められますので、いい感じに認証してください。

最後に以下のような表示になれば成功です。

...
Authenticate your account at:
https://www.npmjs.com/auth/cli/...
Press ENTER to open in the browser...
+ @ky-y./private-library-0918@0.0.1

npm.jsの公開したパッケージにアクセスした時に以下のようにPrivateになっていればOKです。
きちんとPrivate Packageとして公開されています。

5, 使ってみる

適当にプロジェクトを作成してください。

$ mkdir usage-0918
$ cd usage-0918
$ yarn init -2
$ yarn add -D typescript ts-node
$ npx tsc --init

ここからがポイントです。

.yarnrc.yml
yarnPath: .yarn/releases/yarn-4.5.0.cjs

+nodeLinker: node-modules

+npmScopes:
+  ky-y.:
+    npmAlwaysAuth: true
+    npmAuthToken: ${NPM_TOKEN}
+    npmRegistryServer: "https://registry.npmjs.org"

npm.jsから、読み取り専用のNPM_TOKENを発行してあげてnpmAuthTokenに設定してください。
npmScopesky-y.は皆さんのスコープに置き換えてください。

セキュリティ?ローカルだけでやるから問題ない!という場合は、npmAuthToken: "npm_..."と書いてしまっても最悪大丈夫です。
セキュリティを気にする良い子の皆さんには付録に書いてある2通りいずれかの方法をお勧めします。

$ yarn add @ky-y./private-package-0918

上記コマンドを実行すると、先ほど公開(限定公開)したライブラリをインストール可能です。
(人によって、スコープや名称は違うと思うので適時調整してください。

あとは利用するだけですね。

test.ts
import { Hoge, Piyo } from "@ky-y./private-library-0918";

const hoge1 = new Hoge("aiueo");
const hoge2 = new Piyo(hoge1);

hoge1.output();
console.log(hoge1.response(), hoge2.response());
console.log(hoge1.response() === hoge2.response());
$ ts-node test.ts
aiueo
aiueo aiueo
true

ここまでできれば完璧ペキ子ちゃんですね。

パッケージのアップデート

ライブラリ側

ローカル

$ npm version patch
$ npm publish

簡単ですね。
しかし、「お主!リリース前にGitにpushしろよ!」と怒られる場合があります。そういう時は以下のオプションをつけたコマンドに置換してください。

$ npm version patch --no-git-tag-version
$ npm publish

GitHub Actions

.github/workflows/release.yml
name: Publish Package to npmjs

on:
    push:
        branches:
            - main

jobs:
    publish:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4

            - uses: actions/setup-node@v4
              with:
                  node-version: '22.x'
                  registry-url: 'https://registry.npmjs.org'

            - name: Cache Dependencies
              id: yarn_cache
              uses: actions/cache@v4
              with:
                  path: '**/node_modules'
                  key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

            - name: Install Dependencies
              if: steps.yarn_cache.outputs.cache-hit != 'true'
              run: yarn install

            - name: Publish Package to npmjs
              run: |
                  npm run build
                  git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
                  git config --local user.name "github-actions[bot]"
                  npm version patch
                  git push
                  npm publish
              env:
                  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

事前にActionsのSecretにnpm.jsから吐き出した書き込み権限があるトークンを設定してください。

導入側

$ yarn up @ky-y./private-library-0918

付録:NPM_TOKENをいい感じに運用する

.envに直書き

.env
NPM_TOKEN="npm_..."

.envを.gitignoreに設定すればいい感じですね。

1Passwordの秘密参照を使う

自分はこっちを使ってます。

.env
NPM_TOKEN="op://Personal/~~~~~/credential"

op://Personal/~~~~~/credentialは、1PasswordにNPM_TOKENを登録後、下矢印をクリックし「秘密参照をコピーする」をクリックすると取得可能です。

~/.zshrc
alias op-run='op run --env-file=.env /bin/zsh'

導入側のプロジェクトディレクトリでop-runとTerminalで実行すると、1Passwordの認証画面が開きます。
認証完了後、元の表示に戻っているようですが、NPM_TOKENには1Password内に登録された値が格納されています。チーム開発とかに便利ですね。

/Volumes/Projects/ky-y./usage-0918 $ op-run
/Volumes/Projects/ky-y./usage-0918 $
# ↑ op runのセッション中。NPM_TOKENが環境変数に格納されてる。

注意点としては、op runのセッション中でないとyarnの全てのコマンドが利用できません。

Discussion