個人でメンテナンスしている複数のExpo(EAS)プロジェクトのアプリ情報を一括で確認するためのCLIツールをnpmに公開したのですが、色々と問題にぶつかったので、公開までの事前準備や実際の気づき、設計上の判断など、やったことを記事にまとめました。
- 【実際に作成したnpmパッケージ】https://www.npmjs.com/package/expo-app-info
忙しい方へ(要約)
- 【事前準備】Expoアカウント、GitHubアカウント、npmアカウント
- 【背景】Expo内で複数プロジェクトを個人で運用する中で、アカウント全体のアプリとビルド状況を横断的に確認する手段がなかったため、EASのGraphQL APIを直接叩くCLIを作ることにしました
- 【工夫したこと】①アクセストークンを安全に扱うための設計、②GitHub ActionsからnpmへTrusted Publishing(OIDC)で自動公開する仕組み作り、③出力フォーマット(
--json/--csv)の後方互換性ポリシーの決定 - 【気づき】パッケージ名の類似判定によるE403エラーの実例と対処法、npm unpublishの制約について整理しました
事前準備
npmパッケージを公開するにあたり、あらかじめ以下のアカウント・環境を用意しました。
- Expoアカウント:personal access token(PAT)が認証で必要(expo.dev/settings/access-tokens)
- GitHubアカウント:ソースコード、バージョン管理等で必要
- npmアカウント:パッケージ公開で必要
- Node.js 20以上(開発自体は22 LTS以上を推奨)、npm CLI 11.5.1以上(Trusted Publishingの利用に必須)
背景(モチベーション)
複数のExpo/EASプロジェクトを個人で運用していると、「今どのアプリがどのバージョンでビルドされているか」を横断的に確認する手段がないことに気づきました。
-
eas build:listはプロジェクトディレクトリの中でしか実行できず、1プロジェクトずつしか確認できない -
eas project:listのようなアカウント全体を見渡すコマンドは存在しない - Expoのダッシュボードは、アカウント内の各プロジェクトを1つずつクリックして確認する必要がある
毎回ダッシュボードを開いてクリックして回るのが手間だったため、EASのGraphQL APIを直接叩いて、アカウント全体のアプリと最新ビルドバージョンを一覧表示するCLIを作ることにしました。
【補足】EASのGraphQL APIについて(2026/07/29時点)
このCLIが呼び出しているEAS GraphQL API(https://api.expo.dev/graphql)はExpo社が外部の開発者向けに正式サポート・バージョン管理して公開しているAPIではないため、公式ドキュメントは存在しません。そのため、GitHubのソースコードをClaudeに調べてもらい、発見しました。
やったこと
工夫したこと
①依存ゼロでアクセストークンを安全に扱う設計
CLIが読み取る認証情報は環境変数経由のアクセストークン1つに絞り、以下を設計上の制約にしました。
- argv(コマンドライン引数)からは読まない → プロセス一覧経由の漏洩を防ぐ
- ディスクに書き込まない → 設定ファイルへの残留を防ぐ
- 標準出力・エラーメッセージに出さない
- 送信先はAPIエンドポイント1箇所のみ(HTTPSのAuthorizationヘッダー)
あわせて、ランタイム依存をゼロにする方針にしました。Node.jsのグローバルfetchだけでGraphQLリクエストを組み立てているため、公開後にサプライチェーン経由で混入するリスクの対象が自分の書いたコードだけで済みます。
②CIでのリリース自動化(npm Trusted Publishing)
今回、タグpushをトリガーにGitHub ActionsからnpmへTrusted Publishing(OIDC:OpenID Connect)で公開するようにしています。
# .github/workflows/release.yml(抜粋)
on:
push:
tags:
- 'v*.*.*'
jobs:
publish:
environment: npm-publish
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
registry-url: https://registry.npmjs.org
- run: npm install -g npm@^11.5.1
- run: npm ci
- run: npm test
- run: npm publish --provenance --access public
あわせて、依存しているAPIが非公式・非バージョン管理であることを踏まえ、週次でCLIを実データに対して実行し、失敗したら自動でIssueを起票するcanary(監視用)workflowも用意しました。API側のフィールド名変更に、利用者より先に気づけるようにするためです。
③出力フォーマットの後方互換性ポリシー
後から --json / --csv 出力を追加する際、「人間向けのtable出力」と「スクリプト向けの構造化出力」で互換性の扱いを最初に分けておくことにしました。
- table出力(デフォルト):列・文言・色は互換性保証の対象外。いつでも変えてよい
-
--json/--csv:semver準拠。既存フィールドの削除・意味変更はメジャーバージョンアップ時のみ。新規フィールドの追加はマイナーバージョンで許可
これをREADMEに明記しておくことで、後から機能を追加する際に「これはtable扱いかscript扱いか」で毎回悩まずに済むようになりました。
④バージョン管理とリリースノートの一本化
バージョンアップとCHANGELOG生成にはChangesetsを導入しましたが、途中で「GitHub ReleaseとCHANGELOG.mdの二重管理は不要では」と気づきました。Changesetsのchangelog設定をfalseにしてCHANGELOG.md生成を止め、バージョンのbumpだけをChangesetsに任せることにしました。リリースノート自体はGitHub Release(あらかじめ用意したテンプレートを使用)に一本化しています。
わかったこと
①パッケージ配布時の気づき
最初に決めたパッケージ名は、npm側の「名前類似判定」に引っかかりE403になりました(無関係パッケージと名前が近すぎると判定されました)。
$ npm publish --access public
npm notice
...
npm error 403 403 Forbidden - PUT https://registry.npmjs.org/expo-app-list - Package name too similar to existing package expo-applist; try renaming your package to '@it0/expo-app-list' and publishing with 'npm publish --access=public' instead
...
最終的に、スコープなしの別名に変更することで解決しました。パッケージ名は実装より先に、npm registry上で実際に空いているか確認しておくべきという教訓を得ました。似た名前のパッケージがあると、名前類似判定に引っかかるので注意が必要です。今回、パッケージ名を変更することで私は対応しましたが、他にも方法はいくつかあります。具体的な方法は以下の記事が参考になるかと思います。同じ問題に当たった方の記事で、勉強になりました。
②公開パッケージ削除時の気づき
最初にパッケージを公開した際、やっぱりパッケージを一度削除したいと思い調べていたところ、npm unpublishコマンドを知りました。実行コマンドは以下のとおりです。
# npm unpublish <package name>@<version>
npm unpublish expo-app-info@0.1.0
ただこのコマンドは注意が必要なので備忘も兼ねて、以下に記述します。
【注意】
ざっくりと説明すると以下の2つが覚えておくべき情報になります。
-
npm unpublishは公開後72時間以内しかできない。 - 公開を取り下げてから24時間は同じパッケージバージョンを公開できない。
実際は、パッケージの依存関係や過去1時間のダウンロード数、オーナー数/メンテナー数などあるのですが、個人開発で進める分にはそこまで細かく気にする必要はないかと思います。
- 【公式ドキュメント】
感想
初めてのnpmパッケージ公開でしたが、一番時間がかかったのは実装そのものよりパッケージ名の決定と公開後の運用設計でした。「非公式・非バージョン管理のAPIに依存するCLIをどう安全に運用するか」は実装だけでは解決できない部分で、監視スクリプト(週次canary)や出力フォーマットの互換性ポリシーの明文化など、公開後を見据えた設計を最初のリリースの段階からある程度織り込んでおいてよかったと感じています。また、「依存ゼロ」という制約を最初に決めたことで、Lintツールの選定(複数ツールの組み合わせではなく単一バイナリのツールを選択)など、後々の技術選定でも迷わず答えを出せるようになったのも良かった点でした。
以上です、最後までお読みいただきありがとうございました。

