👏

GitHub Actionsの「なぜか動く」を分解する:npm publishとGITHUB_TOKEN

に公開

GitHub Actionsの「なぜか動く」を分解する:npm publishとGITHUB_TOKEN

最近、非エンジニアの人やエンジニアなりたての人と作業することがあります。
そこで自分が組んだGithub Actionsについて質問をもらいました。

「このyamlってプログラミング言語じゃないのになんでこういうふうに書くことで動くの?」
「初めてみても何が何をしているの全然直観的じゃなくて、これから自分で書ける自信がない・・・」

上記のような言葉をもらいました。

実際、GitHub Actionsを使っていると、コピペでなんとなく動いてはいるものの、「裏で何が起きているのかよくわからない」と感じる自分も瞬間があります。

特に、認証周りやパッケージの公開(Publish)フローです。
「なぜパスワードを入力していないのに公開できるの?」
「勝手にセットされる環境変数の正体は何?」

いわゆる「黒魔術」っぽく見えるこれらの挙動について、以下のYAML(抜粋)を例に、その裏側の仕組みを調べてみました。

jobs:
  generate-sdk:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write
    steps:
      - uses: actions/setup-node@v4
        with:
          registry-url: "https://npm.pkg.github.com"
      
      # (中略)

      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1. 認証の正体:GITHUB_TOKEN

まず一番の疑問である「なぜ認証が通るのか」についてです。
通常、手元のPCから操作する場合はPAT(Personal Access Token)を使いますが、Actions内では GITHUB_TOKEN を使うのがベストプラクティスとされています。

どこから来たのか?

このトークンは、Workflowのジョブが開始されるたびにGitHub Actionsが自動生成し、ジョブ終了とともに破棄される仕組みです。「その場限りの使い捨て合鍵」のようなものですね。

なぜ書き込めるのか?(permissionsの重要性)

デフォルトの状態では、このトークンは読み取り専用(Read-only)になっています。
そこで重要になるのが permissions ブロックです。

permissions:
  contents: write  # git pushするため
  packages: write  # npm publishするため

ここで明示的に「書き込み権限(write)」を要求することで、パッケージ公開が可能になります。
逆に言えば、PATをSecretsに登録する必要はない、ということになります。 同じリポジトリ(組織)内での操作であれば、permissions を適切に設定した GITHUB_TOKEN でセキュアに完結できるようです。

2. npm publish が成功する「仕掛け」

次に、npm publish の裏側です。
ここには setup-node アクションと npm コマンドの間で、見えない連携プレーが存在します。

setup-node の隠れた仕事

以下のステップを実行したとき、何が起きているのでしょうか。

- uses: actions/setup-node@v4
  with:
    registry-url: "https://npm.pkg.github.com"

実はこのアクション、Node.jsをインストールするだけでなく、ホームディレクトリに .npmrc(設定ファイル)を自動生成するという役割も担っています。
中身はおおよそ以下のようになっています。

# 自動生成された .npmrc のイメージ
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
registry=https://npm.pkg.github.com

ポイントは、トークンが直接書き込まれるのではなく、${NODE_AUTH_TOKEN} という「環境変数を参照せよ」という命令が書き込まれている点です。

環境変数の注入

そして、実際にPublishするステップで「実体」を注入します。

- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ここで初めて「環境変数 NODE_AUTH_TOKEN」に「GITHUB_TOKEN の値」が渡されることになります。

  1. setup-node が「NODE_AUTH_TOKEN を見るような設定ファイル」を作る。
  2. npm publish 実行時に env で値を流し込む。
  3. npmコマンドが .npmrc を読み、認証が通る。

この2段階のステップによって、コード内にトークンをハードコードすることなく認証を実現しているわけです。
NODE_AUTH_TOKEN という名前はマジックナンバーというわけではなく、setup-node が生成する .npmrc の仕様に合わせたものになっています。

3. ステップ間で変数を共有する

YAML内で、あるステップで作った変数(例えばバージョン番号など)を、後のステップで使いたいケースがあります。

# 古い書き方(現在も動くが非推奨)
echo "NEW_VERSION=1.2.3" >> $GITHUB_ENV

以前はこのように $GITHUB_ENV ファイルに追記する方法が使われていました。
しかしこの方法は、後続の全てのステップに意図せず影響を与えてしまう可能性があるため、現在では非推奨とされています。

現在推奨されているのは、ステップの「出力」として設定する方法です。

- id: setup_version # 1. stepにidを付ける
  run: echo "new_version=1.2.3" >> $GITHUB_OUTPUT # 2. $GITHUB_OUTPUT に書き込む

- run: echo "バージョンは ${{ steps.setup_version.outputs.new_version }} です" # 3. id経由で参照する

このように $GITHUB_OUTPUT を使うことで、どのステップで生成された値なのかが明確になり、ワークフローの見通しが良くなります。

まとめ

GitHub Actionsの挙動は一見「黒魔術」に見えますが、分解すると以下のシンプルな要素の組み合わせで動いているというわけです。

  1. GITHUB_TOKEN: 自動発行される使い捨てトークン。
  2. Permissions: トークンへの権限付与。
  3. Config Generation: Actionによる設定ファイル(.npmrc)の自動生成。

特に setup-node を使う際は、READMEの仕様を一読しておくと「なぜ動くのか」が腹落ちしやすいかと思います。

参考リンク(一次情報)

Discussion