コピペはやめよう:GitHub ActionsでPyPIとnpmのリリースを自動化する方法

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

メンテナンスの罠:なぜコードのコピペが生産性を損なうのか

多くの開発者は、プロジェクト間で同じヘルパー関数をコピーし続けることに疲れ果て、いつか壁にぶつかります。日付フォーマット用の完璧なユーティリティや、カスタムAPIラッパーを書いたとしましょう。2週間後、新しいプロジェクトを始めたあなたは、あの特定のファイルを探して古いフォルダをあさり始めます。ファイルをコピーして使い始めますが、そこでバグを見つけます。すると、両方の場所で修正しなければなりません。これを10個のプロジェクトで繰り返せば、あなたはもはや開発者ではなく、「手動同期エンジン」になってしまいます。

最近、12人のチームと一緒に仕事をしましたが、そこではすべてのマイクロサービスが独自バージョンの「ロガー」ユーティリティを持っていました。新しいセキュリティ要件を満たすためにログ形式を更新する必要が生じた際、15もの異なるリポジトリを開かなければませんでした。わずか10行のコード変更を適用するためだけに、40時間もの反復的な手動作業を費やしたのです。これはエンジニアリングの才能の大きな無駄遣いでした。

根本原因:パッケージ配布の摩擦

なぜライブラリを作る代わりにコピペを続けてしまうのでしょうか? 通常、それは初期設定が重労働に感じられるからです。多くの開発者は、特に以下の3つのハードルに直面します。

  • ツール選びの混乱: Pythonなら setup.pyPoetryHatch のどれを使うべきか、あるいは JavaScript の package.json で複雑な exports をどう管理するかで悩みます。
  • 安定性のリスク: 堅牢なテストスイートがない状態で、小さな変更が下流のプロジェクトを壊してしまうのではないかという不安があります。
  • 手動リリースのストレス: ローカルのターミナルからアップロードする際に、誤って壊れたバージョンを公開したり、機密性の高いAPIキーを漏洩させたりすることへの恐怖です。

標準化されたワークフローがなければ、ライブラリを作成する「コスト」はコピペの「コスト」よりも高く感じられます。しかし、長期的な技術的負債は常に高くつくものです。

共有方法の検討

自動化に飛びつく前に、多くのチームが共有問題をどのように解決しようとしているかを見てみましょう。

  1. Git Submodules: あるリポジトリを別のリポジトリの中にリンクさせます。効率的に聞こえますが、バージョン管理は往々にして悪夢となります。権限が完全に同期されていないと、CI/CDパイプラインが頻繁に壊れます。
  2. 社内共有フォルダ: 全員が同じローカルネットワーク内にいる場合にのみ機能します。規模を拡大したり、より広いコミュニティとコードを共有したりする必要が出た瞬間に破綻します。
  3. パブリックレジストリ(npmおよびPyPI): これが業界標準です。セマンティックバージョニング(SemVer)、pipnpm による簡単なインストール、および自動化された依存関係管理を提供します。

本当の効率化は、リリースの自動化によって得られます。個人のノートPCから npm publishtwine upload を実行する必要はもうありません。

ステップ1:成功のためのプロジェクト構成

パッケージマネージャーに認識されるためには、ライブラリには特定のレイアウトが必要です。以下は、クロスプラットフォームプロジェクト向けのクリーンな構造です。

Pythonライブラリ(PyPI)の場合

現代の Python 開発では pyproject.toml を使用します。このファイルは PEP 517 および PEP 518 標準に従っており、断片化されていた setup.pyrequirements.txt によるアプローチに代わるものです。

my-python-lib/
├── src/
│   └── my_library/
│       ├── __init__.py
│       └── core.py
├── tests/
│   └── test_core.py
├── pyproject.toml
├── README.md
└── LICENSE

pyproject.toml でビルドシステムとメタデータを定義します:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-awesome-lib"
version = "0.1.0"
description = "データ処理のための高性能ユーティリティ"
requires-python = ">=3.8"
authors = [{ name = "あなたの名前", email = "[email protected]" }]

JavaScript/TypeScriptライブラリ(npm)の場合

npm の場合、package.json が核となります。ユーザーに自動的に型定義を提供できるよう、ライブラリには TypeScript を使用することをお勧めします。

my-js-lib/
├── src/
│   └── index.ts
├── dist/
├── tests/
│   └── index.test.ts
├── package.json
├── tsconfig.json
└── README.md

package.json では、パッケージを軽量に保つために files 配列を指定します。ソースコードではなく、コンパイル済みの dist フォルダのみを含めるようにします:

{
  "name": "@your-username/my-js-lib",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "test": "vitest run"
  }
}

ステップ2:信頼性の高いテストを書く

テストなしでライブラリを公開してはいけません。ライブラリが壊れれば、それに依存するすべてのアプリケーションが壊れます。私は、安定性が不可欠な本番環境でこの手法を採用してきました。Python の場合pytest、JavaScript の場合は現在最も高速でモダンな選択肢である vitest を使用します。カバレッジ100%を追い求める必要はありません。それよりも、公開 API のテストに集中してください。「ユーザーが X を入力したとき、常に Y が返されるか?」を確認するのです。

# Pythonのシンプルなテスト例
def test_addition():
    from my_library.core import add
    assert add(2, 3) == 5

ステップ3:GitHub Actionsでリリースを自動化する

GitHub で新しい「リリース(Release)」タグを作成するたびに、自動化が実行されます。これにより、クリーンなビルド環境が保証され、「自分のマシンでは動いた」という言い訳が通用しなくなります。

PyPIワークフロー(Trusted Publishersを使用)

.github/workflows/pypi-publish.yml を作成します。PyPI は OIDC による「Trusted Publishing」をサポートするようになりました。これにより、GitHub Secrets にパスワードやトークンを保存する必要がなくなります。

name: PyPIへの公開
on:
  release:
    types: [published]
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - name: Pythonのセットアップ
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: ビルドと公開
        run: |
          pip install build
          python -m build
      - uses: pypa/gh-action-pypi-publish@release/v1

npmワークフロー

.github/workflows/npm-publish.yml を作成します。これを機能させるには、GitHub リポジトリの Secrets に NPM_TOKEN を追加する必要があります。

name: npmへの公開
on:
  release:
    types: [published]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

メンテナンスのベストプラクティス

コードを公開するのは戦いの半分に過ぎません。あなたのライブラリを他の人にとって有用なものにするために、以下の4つの原則を心に留めておいてください:

  • セマンティックバージョニング: MAJOR.MINOR.PATCH 形式を使用します。既存のコードを壊すような関数名の変更を行う場合は、メジャー(Major)バージョンを上げてください。
  • READMEはUIである: READMEのないライブラリは、事実上存在しないのと同じです。ユーザーがコピーしてすぐに結果を確認できるような、5行程度のコードスニペットを含めましょう。
  • ライセンスを選択する: 最大限に活用してもらうために、MIT または Apache 2.0 を使用してください。多くの企業では、明確な LICENSE ファイルがないコードの使用が法的に禁止されています。
  • 依存関係を最小限に抑える: ライブラリを追加するたびに、それはユーザーの負担になります。自分で10行の関数を書けるのであれば、200KBの依存関係を追加するのは避けましょう。

コードをグローバルなレジストリに移行すると、開発に対する視点が変わります。よりクリーンなインターフェースを設計し、より良いドキュメントを書くことが求められるようになります。一度 GitHub Actions のパイプラインが稼働すれば、新しいバージョンのリリースはボタンをクリックするのと同じくらい簡単になります。このワークフローは共有の摩擦を取り除き、ファイルの管理ではなく機能の構築に集中することを可能にしてくれます。

Share: