スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

GitHub Packages + Changesets + Vite で社内npmパッケージを作成・運用する

こんにちは。技術戦略フロントエンドグループの @kamatte-me と申します。
スタディサプリ開発チームでは、社内向けのプライベートnpmパッケージをGitHub Packagesで運用しています。

本記事では、npmパッケージの作成方法から、“ゆるく”運用していくための仕組み作りまでご紹介します。

背景

スタディサプリには多くのフロントエンドプロダクトがあります。しかし、それぞれのプロダクトでは、同じようなコードが点在していました。ある仕様が変わったら全てのプロダクトコードに修正を加える必要があります。ほぼ同じ作業を何度も、しかもプロダクトによってコーディング規約が違っていたり…これは非常に面倒です。

そこで、こういった共通コードをプライベートnpmパッケージとして作成・配布し、各プロダクトにインストールする手法をとることにしました。
npmパッケージとして公開するメリットは、単にコードの共通化だけではありません。次のようなメリットもあります。

  • 各プロダクトのRenovateで自動的にバージョンアップできる
  • 新バージョンでバグがあった場合、以前のバージョンを利用できる
  • バージョンごとのチェンジログを管理できる

プライベートnpmパッケージの配布レジストリにはGitHub Packagesを利用することにしました。スタディサプリ開発チームではGitHub Enterprise Cloudを契約しており、我々の利用用途なら無料枠内で運用できます。

また、npmパッケージを作成・公開した後の運用もできるだけ楽にしたいと考えました。プロダクト本体の開発に集中したいものです。パッケージの運用に手はかけたくありません。次のような状態を目指しました。

  • チェンジログやバージョニングなどの人の手間は少なく
  • リリースは自動化する
  • 各種設定は簡素に

作った仕組み

こちらがサンプルのリポジトリです。1つのGitHubリポジトリで複数のnpmパッケージを管理できるmonorepo対応版もご用意しています。

github.com

monorepo対応版: kamatte-me/github-packages-npm-monorepo-template

本記事では上のリポジトリを元に解説していきます。なお、このリポジトリをコピーして、READMEの手順に従ってセットアップすれば、すぐにnpmパッケージの作成・運用を始められます。

利用している主なツールは下記の通りです。

これから「パッケージ作成編」と「パッケージ運用編」に分けて解説していきます。

パッケージ作成編

パッケージに求められること

パッケージの使い勝手は非常に重要です。もちろんパッケージ自体のインターフェースもそうなのですが、今回はビルド成果物の形式に着目します。我々のチームでは、以下の要件を設定しました。

  • ESModule/CommonJS 両形式で出力する
  • TypeScriptの型定義を提供する
  • Tree Shakeableであること

各要件の詳細については追って説明していきます。

Viteのライブラリモードを利用して、上記の要件を満たすように設定を行います。

余談: Vite以外のビルドツール

tsup(メンテナンス終了)やその後継のtsdownなどのビルドツール、あるいは純粋にRollupを利用する方法もあります。
今回Viteを利用した理由は、利用者数の多さや開発のアクティブさも大きいですが、何よりも汎用性の高さです。
例えば、ReactやVueなどのフレームワークを利用したコンポーネントライブラリを作成する場合、Hot Module Replacement(HMR)を利用できるため開発体験が非常に良いです。このように、Viteはさまざまな用途のパッケージ開発に対応できます。

Viteの設定

最終的にViteの設定は以下のようになります。上述した要件を満たすようにしています。これがパッケージ作成編の肝です。

// vite.config.ts
import { copyFileSync } from 'node:fs';
import { resolve } from 'node:path';

import { nodeExternals } from 'rollup-plugin-node-externals';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';

export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  build: {
    lib: {
      entry: [resolve(__dirname, 'src/index.ts')],
      formats: ['es', 'cjs'],
    },
    emptyOutDir: true,
    minify: false,
    rollupOptions: {
      output: {
        preserveModules: true,
      },
    },
  },
  plugins: [
    nodeExternals(),
    dts({
      rollupTypes: true,
      afterBuild: () => {
        copyFileSync('dist/index.d.ts', 'dist/index.d.cts');
      },
    }),
  ],
});

いくつか重要なポイントを解説していきます。

ESModule/CommonJS 両形式で出力する

最近の潮流ではESModuleが主流です。しかしながら、レガシーなアプリケーションやフレームワークではCommonJSが現役で利用されており、ESModule形式のパッケージを利用するのに工夫が必要となることがあります。そのため、ESModuleとCommonJSの両方を出力するようにしています。

// vite.config.ts
export default defineConfig({
  ...
  build: {
    lib: {
      ...
      formats: ['es', 'cjs'],
    },
  },
  ...
});

Tree Shakeable なパッケージとしてビルドする

Tree Shakingとは、利用されていないコードを削除することで、バンドルサイズを小さくすることができる仕組みです。WebpackやViteなどの昨今のバンドラーにはこの機能が搭載されています。

How To Make Tree Shakeable Libraries | Theodo によれば、Tree Shakingが有効に働いてくれるためには、以下の3つの条件を満たす必要があります。

  1. ESModule形式で配布する
  2. ライブラリのモジュールツリーを維持し、コードを小さなモジュールに分割して配布する
  3. コードが副作用を持たないこと

1については前項で設定済みです。

2については、Viteのbuild.rollupOptions.output.preserveModulesオプションをtrueにすることで実現できます。

// vite.config.ts
export default defineConfig({
  ...
  build: {
    ...
    rollupOptions: {
      output: {
        preserveModules: true,
      },
    },
  },
  ...
});

これにより、ビルド成果物のファイル構成がソースコードのファイル構成と同じになります。
例えば、src/subdir/fuga.tsというモジュールがあるとします。

// src/subdir/fuga.ts
import type { User } from '@/types/user';

export const fuga = (user: User): string => user.name;

そして、パッケージのエントリポイントとなるsrc/index.tsfugaをexportします。

// src/index.ts
export { fuga } from '@/subdir/fuga';

すると、src配下のディレクトリ構造を保ったまま、dist/subdir/fuga.jsというファイルがビルドされます。

3については、「import時に1つ以上のexportを公開する以外の特別な動作を実行するコード」を実装しなければ良いです。逆に、この動作が必要(例えば、グローバル変数を変更するなど)な場合は、Tree Shakingは諦めてください。
また、バンドラーへのヒントとして、パッケージのpackage.jsonsideEffectsフィールドをfalseに設定します。こうすることで、バンドラーはパッケージ内の全てのモジュールが副作用を持たないと判断できます。

// package.json
{
  ...
  "sideEffects": false,
  ...
}

型定義を提供する

vite-plugin-dtsを使用して、TypeScriptの型定義ファイルを出力します。

  • rollupTypes: true にすることで、パッケージ内の型定義を dist/index.d.ts 1ファイルにまとめて出力します。
    • また、これを設定しない場合、ビルドの対象外のファイル分まで型定義ファイルが出力されてしまいます。
  • CommonJS形式でも型定義を提供するため、afterBuildの処理でdist/index.d.tsを複製して.cts版も作成します。
// vite.config.ts
import dts from 'vite-plugin-dts';
...
export default defineConfig({
  plugins: [
    ...
    dts({
      rollupTypes: true,
      afterBuild: () => {
        copyFileSync('dist/index.d.ts', 'dist/index.d.cts');
      },
    }),
  ],
});

package.jsonには、出力するESModule/CommonJS両方の型定義ファイルのパスを記載します。こうすることで、パッケージにTypeScriptの型定義が内包されます。

// package.json
{
  ...
  "exports": {
    ".": {
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      },
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    }
  },
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  ...
}

外部パッケージをビルドから除外する

これは今まで挙げていませんでしたが、基礎部分での重要なポイントのため明記しておきます。

例えば、パッケージ実装で外部パッケージをimportしているとします。

import { someFunction } from 'some-external-package';

何も設定しないと、some-external-packageパッケージのコード本体もビルド成果物に同梱されてしまうのです。

前提として、パッケージのpackage.jsondependenciespeerDependenciesに記載する依存パッケージは、パッケージ利用者側でもインストールされます。 外部パッケージのimport文を残したままでビルドし、実際の依存パッケージ本体は利用者側でインストール・利用されるのが正しい振る舞いです。 外部パッケージのコード本体をバンドルしてしまうと、無駄にバンドルサイズが膨らむだけでなく、パッケージ利用者側で依存パッケージのアップデートが実質的にできなくなってしまいます。

そこで、rollup-plugin-node-externalsを使用します。これは外部パッケージをビルドから除外してくれるパッケージです。外部パッケージのimport文(CommonJSならrequire)をそのまま残してくれます。

// vite.config.ts
import { nodeExternals } from 'rollup-plugin-node-externals';
...
export default defineConfig({
  ...
  plugins: [
    nodeExternals(),
    ...
  ],
});

なお、package.jsondependenciespeerDependencies, devDependenciesなどは、パッケージを配布するにあたり適切に定義すべきものです。npmのドキュメントを参考に理解を深めておくとよいでしょう。

docs.npmjs.com

その他ツールの採用理由

Vite以外に採用しているツール群について、採用理由を簡単に説明します。
単にモダンだからという理由だけで採用しているわけではありません。リスクを減らしたり、メンテナンスの手間を減らすことを重視しています。あまり頑張らずに運用していきたいのです。

  • pnpm
    • npmやyarnよりも高速で、ディスク容量の節約にもなります。
    • ただ一番大きな採用理由は、Phantom Dependencies(コードで使用されているものの、package.jsonで明示的に宣言されていないパッケージ)を防止できるためです。
  • Vitest
    • テストランナーです。Viteと統合して利用できます。
    • Jestだと設定が分かれてしまい、メンテナンスに手間がかかるため採用しませんでした。
  • Biome
    • ESLint/Prettierに変わる新しいリンター/フォーマッターです。
    • ESLintは設定やプラグインが複雑になりがちで、メンテナンスに手間がかかるため採用しませんでした。
    • サンプルリポジトリでは設定(biome.json)も限りなくシンプルにしています。小さなパッケージであれば、設定を煮詰める手間を掛けずともこのままで十分です。
  • publint
    • パッケージの公開前に、package.jsonの設定漏れや誤りを検出してくれます。

パッケージの実装

パッケージはTypeScriptで実装します。

Viteで設定しているエントリファイルはsrc/index.tsです。ここで、パッケージの公開APIをexportします。
サンプルリポジトリでは、次のようにメソッドや型をexportしています。

// src/index.ts
export { hoge } from '@/hoge';
export { fuga } from '@/subdir/fuga';
export type { User } from '@/types/user';

このようにすると、パッケージの利用者側では、次のようにimportできるようになります。

import { hoge, fuga, type User } from '@<your-org>/<your-package>';

よくあるnpmパッケージっぽいですね。

実装ができたらビルドしてみましょう。

pnpm build

distディレクトリにビルド成果物が出力されます。

dist/
  index.js
  index.cjs
  index.d.ts
  index.d.cts
  hoge.js
  hoge.cjs
  subdir/
    fuga.cjs
    fuga.d.ts
    fuga.js

先ほど設定したように、ESModule形式 .js とCommonJS形式 .cjs の両方が出力されていることが分かります。
また、型定義は index.d.[ts|cts] にまとめて出力されています。src/index.tsでexportした関数や型の型定義がexportされます。

// dist/index.d.ts
export declare const fuga: (user: User) => string;

export declare const hoge: (user: User) => number;

export declare interface User {
  id: number;
  name: string;
}

export { };

このようにパッケージを実装していきましょう。続く運用編にて、パッケージをGitHub Packagesにリリースする方法を説明します。

パッケージ運用編

バージョニングとリリース

いよいよリリースです。Changesetsというツールを利用します。

github.com

Changesetsでは次のフローでリリースができます。

  1. PRを作成
  2. PRに付くコメントからchangesetファイル(アップデートタイプ, 変更内容を記述する)を作成
  3. PRをマージ
  4. Changesetsがリリース用のPRを作成
  5. 4のPRをマージすると、GitHub Packagesにリリースされる

とても楽ですね。

Changesetsは次のことを自動化してくれます。

  • チェンジログの自動生成
  • セマンティックバージョニング
  • npmパッケージのリリース
  • GitHub Releaseの作成

人がやることはたった2つだけです。

  1. changesetファイルを作成する
  2. リリース用のPRをマージする

少しだけ労力を掛けるべきは、「changesetファイルを作成する」ことだけです。このファイルがリリースバージョンの決定とチェンジログの生成を司ります。
下記のいずれかの方法で作成してPRに含めます。

  • pnpm changesetを実行して対話形式で作成
  • PRへのchangeset-botのコメントにあるリンクから作成

案内に従ってアップデートタイプ(major/minor/patch)と変更内容を記述します(monorepoの場合は対象のパッケージ選択も必要)。

例えば次のようなファイルを作成すると

// .changeset/<random-file-name>.md
---
"@<your-org>/<package-name>": minor
---

Add `fuga` function

リリース時、マイナーバージョンアップデートとしてリリースされ、次のようなチェンジログが生成されます。

// CHANGELOG.md
## 0.1.0

### Minor Changes

#123 1234abc Thanks @kamatte-me! - Add `fuga` function

複数のPRがマージされてchangesetファイルが複数存在する場合も、Changesetsが自動的にバージョンを決定してくれます。これがChangesetsの一連のリリースフローです。

余談: 他のリリース自動化ツール

semantic-releaseが有名です。これは、Conventional Commitsに従ったコミットメッセージに基づいて自動的にバージョニング・リリースしてくれるものです。 これはこれで便利なのですが、コミットメッセージのルールをチーム全体で徹底する必要があり、その文化が無い我々にとってはハードルが高かったです。その点Changesetsは、changesetファイルの中身だけ気にすれば良いのでだいぶゆるく運用できます。

Changesetsのセットアップ

まずは@changesets/cli@changesets/changelog-githubをdevDependenciesとしてインストールします。その後、pnpm changeset initを実行して、Changesetsの初期設定を行います。

pnpm install -DE @changesets/cli @changesets/changelog-github
pnpm changeset init

.changeset/config.jsonが作成されます。changelogには@changesets/changelog-githubを使用します。これはGitHubのPRリンク付きでチェンジログを生成してくれるものです。また、"access": "restricted"(非公開としてリリース)に設定しておきます。

// .changeset/config.json
{
  "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "<your-org>/<repository-name>" }
  ],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

なお、monorepoの場合も設定は同じです。Changesetsがpnpm Workspaceを自動的に認識してくれます。

次に、GitHub Actionsの設定を行います。.github/workflows/release.ymlを作成し、下記のように設定します。

# .github/workflows/release.yml
name: Release

on:
  workflow_run:
    workflows:
      - CI
    branches:
      - main
    types:
      - completed

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    permissions:
      contents: write
      issues: write
      pull-requests: write
      packages: write
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v5
        with:
          node-version-file: .node-version
          cache: pnpm
          registry-url: 'https://npm.pkg.github.com'
      - run: pnpm install
      - uses: changesets/action@v1
        with:
          version: pnpm changeset:version
          publish: pnpm changeset:publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

また、このワークフローで利用するnpm scriptsも設定しておきましょう。

// package.json
{
  ...
  "scripts": {
    ...
    "changeset:version": "changeset version && pnpm lint:fix",
    "changeset:publish": "pnpm build && changeset publish"
  },
}

mainブランチのCIワークフローが成功した後に、このReleaseワークフローが実行されます。 changesets/actionリポジトリの状態を見て、次のいずれかの動作を行います。

  • 通常: changesetファイルに基づきリリース用PRを作成・更新
    • リリース用PRには、package.jsonのバージョンアップとCHANGELOG.mdの更新が含まれます。
  • リリース用PRがマージされた場合: リリース実行

最後に、changeset-botというGitHub Appをリポジトリにインストールしましょう。
このBotは、PRにchangesetファイルを作成するためのコメントをしてくれます。これがあるだけでも、運用していく上での心理的負荷がグッと下がります。

Pull RequestでSnapshotリリース

PRの段階でnpmパッケージを試せると便利です。ChangesetsのSnapshotリリース機能を利用できます。

次のワークフローでは、PRにsnapshot-releaseラベルが付与されたときに、0.0.0-pr-<pr-number>-<timestamp>というタグでパッケージをリリースできます。

# .github/workflows/snapshot-release.yml
name: Snapshot Release

on:
  pull_request:
    types:
      - labeled

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    runs-on: ubuntu-latest
    if: contains(github.event.pull_request.labels.*.name, 'snapshot-release')
    permissions:
      contents: write
      issues: write
      pull-requests: write
      packages: write
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v5
        with:
          node-version-file: .node-version
          cache: pnpm
          registry-url: 'https://npm.pkg.github.com'
      - run: pnpm install
      - name: Create changeset
        run: |
          { echo "---"; \
            echo "'@<your-org>/<package-name>': patch"; \
            echo "---"; \
            echo ""; \
            echo "Snapshot release"; \
          } >> .changeset/snapshot-release.md
      - name: Release snapshot to pr tag
        run: |
          pnpm changeset version --snapshot pr-${{ github.event.pull_request.number }}
          pnpm changeset:publish --no-git-tag --tag pr-${{ github.event.pull_request.number }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Get released version
        id: get-version
        run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
      - uses: int128/issues-action@v2
        with:
          context: true
          post-comment: |
            ## スナップショット版がpublishされました

            ```
            @<your-org>/<package-name>@${{ steps.get-version.outputs.version }}
            ```
      - if: always()
        uses: int128/issues-action@v2
        with:
          context: true
          remove-labels: snapshot-release

Snapshotのパッケージは次のようにインストールできます。

npm install @<your-org>/<package-name>@0.0.0-pr-<pr-number>-<timestamp>

以上がChangesetsを利用したバージョニングとリリースの方法でした。晴れてGitHub Packagesにパッケージをリリースできました。

パッケージをインストールする

リリースしたパッケージをプロジェクトにインストールしましょう。
今回はnpm/pnpmを利用する場合の設定方法を解説します。yarnやbunなどでも同等の手段が提供されています。

ホストマシンでのインストール

まず、GitHub Packagesの読み取り権限を持つアクセストークンを発行する必要があります。
GitHubの設定ページでread:packagesスコープを設定したPersonal access token (classic)を発行してください。

https://github.com/settings/tokens

2025/9/26現在、GitHubのドキュメントではPersonal access token (classic)が必須と書かれていますが、OAuth Appトークンでも動作するようです。 非公式な手段であるため今回は紹介しませんが、Organization内に権限を絞れるなど、よりセキュアに運用できます。

発行されたアクセストークンをメモしておいてください。

次にパッケージマネージャーに認証情報を設定します。 インストール先プロジェクトに.npmrcを作成し、以下のように記載します。

//npm.pkg.github.com/:_authToken=<発行したアクセストークン>
@<your-org>:registry=https://npm.pkg.github.com

この.npmrcはGitで管理しないようにしてください。

あとはパッケージをインストールできれば成功です!

npm install @<your-org>/<package-name>
pnpm install @<your-org>/<package-name>

GitHub Actionsでのインストール

GitHub Actionsでパッケージをインストールする場合は、ワークフローのactions/setup-node.npmrcを生成できます。かつ、トークンにGITHUB_TOKENが利用できます。

jobs:
  example:
    ...
    steps:
      ...
      - uses: actions/setup-node@v5
        with:
          node-version-file: .node-version
          registry-url: 'https://npm.pkg.github.com'
          scope: '@<your-org>'
      - run: npm install # pnpm install
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

加えて、GitHub Packagesのパッケージ設定ページ (https://github.com/orgs/<org>/packages/npm/<package-name>/settings) で、対象のリポジトリからのアクセスを許可しておく必要があります。
Manage Actions access > Add repository から、対象のリポジトリReadロールで追加してください。

ワークフローを実行してパッケージがインストールできれば成功です!

自作パッケージ側: Renovateで依存パッケージを自動アップデート

パッケージに含まれる依存パッケージはできるだけ最新の状態に保ちたいものです。特にpeerDependenciesに関しては、メンテナンスしていかなければ利用側のプロジェクトとバージョンの乖離が起きてしまいます。
Renovateを利用して、依存パッケージの更新を自動化しましょう。

まずは、RenovateのGitHub Appをパッケージのリポジトリにインストールしてください。

続いて、リポジトリのルートに設定ファイルrenovate.jsonを作成します。サンプルリポジトリでは下記のように設定します。

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:js-lib"],
  "timezone": "Asia/Tokyo",
  "prHourlyLimit": 0,
  "labels": ["dependencies"],
  "separateMajorMinor": true,
  "automerge": true,
  "packageRules": [
    {
      "matchUpdateTypes": ["major"],
      "automerge": false
    }
  ],
  "lockFileMaintenance": {
    "enabled": true
  },
  "vulnerabilityAlerts": {
    "enabled": true
  }
}

小さな手間を減らすため、メジャーアップデートを除いてオートマージするようにしています。戦略はチームやプロジェクトに合わせて調整してください。
いくつか推奨したいポイントがあるため解説します。

JavaScriptライブラリ向けのプリセットを利用

Renovateが提供しているプリセットconfig:js-libを利用するのがおすすめです。

// renovate.json
{
  ...
  "extends": ["config:js-lib"],
  ...
}

これは、デフォルトのレコメンド設定に加えて、package.jsonの依存関係の更新方法を次のように調整したものです。

  • dependencies: バージョンレンジを保つ(^1.0.0^1.1.0
  • peerDependencies: バージョンレンジを広げる(^1.0.0^1.0.0 || ^2.0.0
  • devDependencies: バージョン固定(^1.0.01.1.0

パッケージの利用者側に影響するのがdependenciespeerDependenciesです。これらの破壊的な変更を避けられます。一方、パッケージの開発時にしか利用しないdevDependenciesは固定しておくことで、lockファイルを見るまでもなく「package.jsonに記載されたバージョンを利用している」ことが明示的になります。

lockファイルの自動再生成

lockFileMaintenanceを有効化すると、pnpm-lock.yamlを自動で最新の状態に更新してくれます。 「依存パッケージの依存パッケージ」まで一括でアップデートしてくれるため、常に最新の依存関係で動作を保てるほか、脆弱性アラートの頻度も減らせます。

// renovate.json
{
  ...
  "lockFileMaintenance": {
    "enabled": true
  },
  ...
}

脆弱性アラートの自動対応

vulnerabilityAlertsを有効化すると、GitHub脆弱性アラートを元に、依存パッケージの更新Pull Requestを自動で作成してくれます。リポジトリ設定でDependency graph, Dependabot alertsを有効化しておく必要がありますので、ドキュメントを参考に設定してください。

// renovate.json
{
  ...
  "vulnerabilityAlerts": {
    "enabled": true
  }
}

dependenciespeerDependenciesに影響がある場合は、自作パッケージの新たなバージョンをリリースすべきです。

以上がRenovate設定のポイントでした。

パッケージ利用側: Renovateで自作パッケージを自動アップデート

さて、パッケージを利用する側のリポジトリのRenovateにもアクセストークンを設定する必要があります。今回はRenovateのCloud版を利用している場合の設定方法を解説します。
Renovateのドキュメントをベースに設定していきます。

docs.renovatebot.com

まずは、Renovate用にGitHubのPersonal access token (classic)を発行しましょう。こちらもスコープにread:packagesを指定してください。

Renovateのドキュメントに従って、developer.mend.io上でアクセストークンをシークレットに登録します。Secret nameは仮にGITHUB_PACKAGES_TOKENとして説明を続けます。

最後に、リポジトリrenovate.jsonに次の設定を追加します。tokenに先ほど登録したシークレットを環境変数のように指定します。

// renovate.json
{
  ...
  "hostRules": [
    {
      "matchHost": "https://npm.pkg.github.com/",
      "hostType": "npm",
      "token": "{{ secrets.GITHUB_PACKAGES_TOKEN }}"
    }
  ],
  "npmrc": "@organizationName:registry=https://npm.pkg.github.com/",
  ...
}

この設定をリポジトリのメインブランチにマージしてください。

最後に動作確認をしましょう。developer.mend.io上から、対象のリポジトリのジョブ実行履歴を確認できます。設定後に実行されたジョブで、パッケージ情報取得時に何もエラーが出ていなければ成功です!

おわりに

スタディサプリ開発チームでの社内npmパッケージの作成・運用方法を解説しました。
初期導入こそ大変かもしれませんが、一度仕組みを作ってしまえば、あとは手放しでも“ゆるく”運用していけます。

我々のチームではGitHub Packages導入以降、共通部分のメンテナンスコストが大きく削減されました。その他にも、npmパッケージにすることでコードを疎結合にできたり、インターフェースを適切に設計するようになったりと、品質的な恩恵も生まれました。

これから社内パッケージを作成しようとしている方、あるいはすでに運用している方にも参考になれば幸いです。