GitHub Packagesを試す
始めに
共通のコードを使い回すためにnpmパッケージ化が案として上がると思いますが、全世界にpublishするほどのものではない場合があると思います。GitHubにはGitHub Packagesというパッケージを配置する場所が用意されており、npmパッケージも同様にpublishすることができるのでそれを試してみました。
プロジェクト構成
この記事では以下のプロジェクト構成で検証しました。
-
wintyo-github-packages (検証するようで用意したGitHub Organization)
- wintyo-github-packages/test-use-packages (他リポジトリで作成したパッケージを使用するリポジトリ)
- wintyo-github-packages/utils (publicなパッケージをpublishするリポジトリ)
- wintyo-github-packages/private-utils (privateなパッケージをpublishするリポジトリ。パッケージがprivateなだけなので、リポジトリ自体はpublic)
publishされたパッケージはこちらで参照できます。privateなパッケージは他の人からは見えないと思います。
https://github.com/orgs/wintyo-github-packages/packages
パッケージ作成
まずはinstallするためのパッケージを作成します。
パッケージコードの用意
ディレクトリ構成は重要なところだけピックアップすると以下のようになります。
├── dist (ビルド後の成果物の出力先)
├── src (ビルド前のソースコード)
│ └── index.ts (ビルド時のエントリーポイント)
└── package.json (パッケージ情報)
今回は検証のためなのでシンプルなメソッドだけexportします。
/**
* 加算メソッド
* @param x - 数値
* @param y - 数値
* @returns - 足した値
*/
export const add = (x: number, y: number) => {
return x + y;
};
package.jsonは以下のように書いています。name
がimport {} from 'xxx'
で使われる名前で、main
がインポート時に参照されるファイルになります。
{
"name": "@wintyo-github-packages/utils",
"version": "0.0.0",
"description": "utility package",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist/*",
"src/*"
],
"scripts": {
"build": "tsc"
},
"author": "wintyo <wintyo1024@gmail.com>",
"license": "MIT",
"devDependencies": {
"typescript": "^4.9.5"
}
}
tsconfig.jsonは以下のようにしています。自動生成したものから必要そうな項目を指定しているのでもしかしたら不要なものとかがいくつか入っているかもしれないですが、そこはご了承ください。
{
"compilerOptions": {
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */,
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
/* Interop Constraints */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */,
/* Completeness */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
GitHub PackagesにpublishするCIを作成
次にCI上でGitHub PackagesにpublishするCIを書きます。以下のCIではタグが振られた時にpublishするものとなります。
CI設定で重要な点は以下の2点です。
- GITHUB_TOKENのpermissionsの設定
- このままだとpublishするための権限がないので、
permisssons.contents
とpermissions.packages
に適切な権限を設定します
- このままだとpublishするための権限がないので、
-
setup-node
でregistry-url
とscope
を設定する- npm publish先はデフォルトだとnpmレジストリになるため、GitHub Packagesに変更します
-
scope
はGitHubのOrganization名または個人アカウント名になります
name: Publish npm package
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
registry-url: https://npm.pkg.github.com
scope: '@wintyo-github-packages'
- name: Install
run: yarn install
- name: Build
run: yarn build
- name: Publish npm package
run: yarn publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish先のURLが違うだけで基本的にはnpmレジストリにpublishするのと同じなので、一度publishしたバージョンは上書きできないので、追加でpublishする場合はpackage.json
のversion
を繰り上げる必要があることに注意してください。
ただGitHub Packagesの場合は手動で該当のバージョンを削除することができるので、一度削除すればpublishし直すことはできると思います(未検証)。
publishに成功するとGitHubリポジトリページの右側のPackagesに表示されます。
https://github.com/wintyo-github-packages/utils
publishしたパッケージをprivateからpublicにする
GitHub Packagesはデフォルトはprivateなので、それをpublicに変更します。
Organization設定からpublicパッケージの設定をできるようにする
そもそもpublicパッケージに切り替えることもOrganizationだと最初はできないので、できるように設定します。
Organizationの設定ページのPackagesのところでpublicに変更できるようにチェックを入れます。
publishしたパッケージの設定画面に入ってpublicに切り替える
先ほどpublishされたことを確認した場所からパッケージの詳細に飛びます。
パッケージの詳細ページの右下にある「Package Settings」をクリックします。
そのページにある「Change Visibility」からpublishに切り替えます。
privateパッケージも用意する
上記の作業をもう一度行って別なパッケージを作成し、パッケージの閲覧権限だけprivateのままにします。
作成したパッケージを使用する
手元で使用する
GitHub Packagesをinstallする場合は一手間が必要です。npm install
またはyarn install
する際に参照するレジストリを切り替える必要がありますが、GitHub Packagesの場合は必ずPATを入れる必要があります(publicなパッケージであっても)。この辺の設定を.npmrc
に書くと以下のようになります。
//npm.pkg.github.com/:_authToken={PATを入れる}
@wintyo-github-packages:registry=https://npm.pkg.github.com
PATの作成方法や.npmrc
の設定の意味はこちらをご参照ください。
これを設定することで後はいつも通りyarn add @wintyo-github-packages/utils
とするだけでinstallされます。
.npmrcを生成するスクリプトを用意
上記の方法で動きますが、PATが含まれているためこのままコミットすることができません。PATは.env
に登録しておいて、それをもとに.npmrc
が生成された方が運用しやすいと思うのでそのスクリプトを作成します。
まずは雛形を用意します。
//npm.pkg.github.com/:_authToken=${GITHUB_PKG_TOKEN}
@wintyo-github-packages:registry=https://npm.pkg.github.com
shellスクリプトからテンプレートエンジンっぽく動かすにはenvsubst
コマンドを使うと良く、以下のようにすることで${GITHUB_PKG_TOKEN}
が差し変わります。
$ export GITHUB_PKG_TOKEN=ghp_XXX
$ cat ./scripts/.npmrc.template | envsubst
envsubst
はこちらの記事を参考にしました。
これを使って.npmrc
ファイルを生成するスクリプトを書きます。
if [ -z $GITHUB_PKG_TOKEN ]; then
echo "GITHUB_PKG_TOKENの環境変数が設定されていません"
exit -1
fi
export GITHUB_PKG_TOKEN=$GITHUB_PKG_TOKEN
cat ./scripts/.npmrc.template | envsubst > .npmrc
最後にsetup.sh
を用意して、これを呼び出すだけで動くようにします。
if [ -f .env ]; then
source .env
fi
source ./scripts/generate-npmrc.sh
GitHub Actionsで使用する
GitHub Actions上ではPATをsecretsに登録して、それを環境変数に入れてsetup.sh
を実行してからyarn install
することで動きます。secretsの登録の際はGITHUB_*
は予約語になっていて使えなかったのでNPM_GITHUB_PKG_TOKEN
という名前にしています。
name: Trial
on: push
jobs:
install-github-packages:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
- name: Setup npmrc
run: ./setup.sh
env:
GITHUB_PKG_TOKEN: ${{ secrets.NPM_GITHUB_PKG_TOKEN }}
- name: Install
run: yarn install
- name: Run
run: yarn start
余談
ちなみにpublicパッケージだけであればトークンはなんでも良さそうで、GITHUB_TOKEN
を使ってもinstallできました。
その他
package.jsonのversionを起点にCIを書く
GitHub Packagesへのpublishでタグが振られた場合にCIを動かす書き方をしましたが、この場合packages.json
のversionが上がっていなかった場合publishに失敗してしまいます。実際のところ、gitのタグというよりpackage.jsonのversionが変わった時に動くと良さそうです。
残念ながらその要望に完全にマッチするやり方は存在しませんが、publish可能ならpublishして、かつpackage.jsonに入っているversionでタグを振っておくCIを書くことができます。
name: Publish npm package
on:
push:
+ branches:
+ - main
tags:
- - 'v*.*.*'
+ - '!*'
permissions:
- contents: read
+ contents: write
packages: write
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
registry-url: https://npm.pkg.github.com
scope: '@wintyo-github-packages'
- name: Install
run: yarn install
- name: Build
run: yarn build
+ # publish可能かのステータスを見る(0が正常。1がエラー)
+ - id: publishable
+ name: Check Publishable
+ run: "\
+ npx can-npm-publish --verbose \
+ && echo status=0 >> $GITHUB_OUTPUT \
+ || echo status=1 >> $GITHUB_OUTPUT"
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # publish可能(まだpublishされていないバージョン)ならpublishする
- name: Publish npm package
+ if: ${{ steps.publishable.outputs.status == 0 }}
run: yarn publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # package.jsonのバージョンを取得する
+ - id: package-version
+ name: Register Package Version
+ run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_OUTPUT
+ # tagが振られてなかったら振る
+ - name: Add Tag
+ uses: pkgdeps/git-tag-action@v2
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ github_repo: ${{ github.repository }}
+ version: ${{ steps.package-version.outputs.PACKAGE_VERSION }}
+ git_commit_sha: ${{ github.sha }}
+ git_tag_prefix: 'v'
以下の記事を参考にしましたが、いくつか変更があります。
- publish可能かを別stepに分ける
- 参考記事ではfallbackを使って1つにまとめて書いてますが、その場合、
yarn publish
で失敗した場合にCIが落ちなくなってしまいます。なので先にpublish可能かのステータスをGITHUB_OUTPUTに出力して、publishステップ自体をスキップできるようにしました。
- 参考記事ではfallbackを使って1つにまとめて書いてますが、その場合、
- tagを振るGitHub Actionsのバージョンを2系にする
- 今参考記事で使っているActionsを見たら2系になっていたので、そちらの書き方に合わせました
- https://github.com/pkgdeps/git-tag-action
以上のやり方でpackage.json
のversion
を上げるだけでpublishしたりタグを振ってくれたりしますが、CI設定の複雑さが大分上がってしまいました。そもそもバージョン上げる際はnpm version minor
とかしたら勝手にpackage.json
のversion
を上げるのとタグ付けを一緒にやってくれてそちらで運用する場合はわざわざここまでしなくても良いのかなと感じました。
終わりに
以上がGitHub Packagesでパッケージをpublishして使用する方法でした。publish自体は簡単にできますが、使うときにPATを生成して.npmrc
の設定をしないといけないのがなかなかネックだなと感じました。SSHで認証できたら今すぐにでも使っていきたいですが、まだ実装の目処は立っていなそうです。
別な手段としてGitHubのリポジトリを直接参照してinstallするやり方も試しており、そちらの方が使い勝手が良さそうな印象でしたので興味がある方はこちらもご参照いただけると幸いです。
Discussion