📝

GitHubリポジトリにあるソースコードをインストールして使う

2023/03/18に公開

始めに

共通のコードを使い回すためにnpmパッケージ化が案として上がると思いますが、全世界にpublishするほどのものではない場合があると思います。publishせずに使用する方法の一つとして、GitHubリポジトリのURLを指定してinstallする手段がありますが、リポジトリは基本ビルド前のソースを配置するものであり、使いづらそうに見えます。しかし、GitHub Actionsを駆使することでビルド済みのパッケージをタグ付きでpushすることができ、そこをinstall先とすることで十分運用できそうに感じたのでその辺についてまとめてみました。

プロジェクト構成

この記事では以下のプロジェクト構成で検証しました。Publicでみれるものはリンクをつけていますが、Privateリポジトリからインストールする検証の都合上見れないページについてはリンクはつけておりません。
npm packageへのインストールは git+ssh://git@github.com:wintyo-ssh-packages/utils.git#dist-v0.0.0 のようにsshで指定することでアクセス権限のあるGitHubユーザは自動でinstallできるようにしています。

パッケージ作成

まずはinstallするためのパッケージを作成します。

パッケージコードの用意

ディレクトリ構成は重要なところだけピックアップすると以下のようになります。

├── dist (ビルド後の成果物の出力先)
├── src (ビルド前のソースコード)
│   └── index.ts (ビルド時のエントリーポイント)
└── package.json (パッケージ情報)

今回は検証のためなのでシンプルなメソッドだけexportします。

src/index.ts
/**
 * 加算メソッド
 * @param x - 数値
 * @param y - 数値
 * @returns - 足した値
 */
export const add = (x: number, y: number) => {
  return x + y;
};

package.jsonは以下のように書いています。nameimport {} from 'xxx'で使われる名前で、mainがインポート時に参照されるファイルになります。

package.json
{
  "name": "@wintyo-ssh-packages/utils",
  "version": "0.0.0",
  "description": "utility package",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  },
  "author": "wintyo <wintyo1024@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "typescript": "^4.9.5"
  }
}

tsconfig.jsonは以下のようにしています。自動生成したものから必要そうな項目を指定しているのでもしかしたら不要なものとかがいくつか入っているかもしれないですが、そこはご了承ください。

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. */,
    "declarationMap": true, /* Create sourcemaps for d.ts files. */
    "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. */
  }
}

パッケージコードをビルドしてdistブランチにpushする

前セクションで用意したコードをビルドしてdistブランチにpushします。同じリポジトリにpushする場合はGitHub Pagesで同じみのpeaceiris/actions-gh-pagesを使うと楽に実装できます。このActionにブランチ名やタグ名、push対象になるディレクトリを指定できるので適切なパラメータを設定します。ディレクトリについては必要なコードだけpushすれば良いためdist,src,package.jsonをpublishディレクトリにコピーしてそれをpushするようにしています。

パッケージコードをビルドしてdistブランチにpushする
name: publish
on:
  push:
    branches:
      - main
    tags:
      - 'v*.*.*'

permissions:
  contents: write

jobs:
  publish:
    runs-on: ubuntu-22.04
    timeout-minutes: 10
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18.12.1'

      - name: Install
        run: yarn install

      - name: Build
        run: yarn build

      - name: Prepare Publish Contents
        run: |
          mkdir publish
          cp -r dist publish
          cp -r src publish
          cp package.json publish

      - name: Prepare tag
        id: prepare_tag
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          echo "DEPLOY_TAG_NAME=dist-${TAG_NAME}" >> "${GITHUB_OUTPUT}"
        env:
          TAG_NAME: ${{ github.ref_name }}

      - name: Publish
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./publish
          publish_branch: dist
          tag_name: ${{ steps.prepare_tag.outputs.DEPLOY_TAG_NAME }}
          tag_message: 'Publication ${{ github.ref_name }}'

これでdistブランチにどんどん成果物がpushされていきます。v0.0.0のようにタグを振るとdist-v0.0.0で成果物もタグを振ってpushされるようになります。これで yarn add git+ssh://git@github.com:wintyo-ssh-packages/utils.git#dist-v0.0.0 のように書くとバージョン指定でinstallすることができます。

Privateパッケージも用意する

上記の作業をPrivateリポジトリも行います。

作成したパッケージを使用する

手元で使用する

手元で使用する場合は yarn add git+ssh://git@github.com:wintyo-ssh-packages/utils.git#dist-v0.0.0 のようにssh指定のGitHub URLでインストールすることができます。認証もGitHubと同じものが使われるのでプライベートリポジトリもアクセス権限のあるGitHubアカウントであれば問題なくインストールできます。
インストールできたらいつも使っているような書き方でimportできます。

import { add } from "@wintyo-ssh-packages/utils";

console.log(add(1, 2));

GitHub Actions上で使用する

CIではプライベートリポジトリはそのままだと認証エラーになってしまうため一工夫が必要です。今回はGitHub Actionsでやる方法を2つ紹介します。

SSH Keyを登録して使用する

一つ目の方法は、SSH KeyをGitHub Actionsに登録してインストールする方法です。手元でやる場合は .ssh/id_rsa をみてSSH認証しているので、同じようにGitHub Actions側でもできるようにします。

ここの記事などを参考に公開鍵・秘密鍵を生成します。公開鍵は記事の通りGitHubアカウントに対してSSH keyを登録します。

https://qiita.com/shizuma/items/2b2f873a0034839e47ce

秘密鍵についてはGitHub Actionsのsecretsに登録し、この変数を使ってGitHub Actions上で鍵として保存し、そのあとyarn installします。具体的には以下のようになります。

SSH Keyを登録して使用する
name: Install by ssh
on: push

jobs:
  install-by-ssh:
    runs-on: ubuntu-22.04
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: ".node-version"
          cache: "yarn"

      - name: SSH Setting
        run: |
          mkdir -p /home/runner/.ssh/
          echo -e "$SSH_KEY" > /home/runner/.ssh/id_rsa
          chmod 600 /home/runner/.ssh/id_rsa
        env:
          SSH_KEY: ${{ secrets.WINTYO_SSH_KEY }}

      - name: Install
        run: yarn install --frozen-lockfile

      - name: Run
        run: yarn start

PATで使用する

二つ目の方法はPersonal Access Token(PAT)を使って認証する方法です。
まずはPATを発行します。classicの方選択して、repoに権限を付与して作成します。これをGitHubのsecretsに登録します。

ssh指定したものをトークン付きのhttpsに切り替える場合はgit configで以下のように設定します。

ssh指定からhttpsに切り替える
git config --global url."https://x-access-token:[トークン]@github.com/[アカウント名]".insteadOf "git@github.com:[アカウント名]"

なので上記の設定をinstall前に書くことで動きます。まとめると、以下のようなGitHub Actionになります。

PATを使ってインストールする
name: Install by PAT
on: push

jobs:
  install-by-pat:
    runs-on: ubuntu-22.04
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.WINTYO_PAT }}

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: ".node-version"
          cache: "yarn"

      - name: Setup ssh url
        run: |
          git config --global url."https://x-access-token:${{ secrets.WINTYO_PAT }}@github.com/wintyo-ssh-packages".insteadOf "git@github.com:wintyo-ssh-packages"

      - name: Install
        run: yarn install --frozen-lockfile

      - name: Run
        run: yarn start

一つ注意がありまして、checkout時にもtokenを設定する必要があります。以下でも書かれてましたが、checkoutで使ったトークン(デフォルトだとGITHUB_TOKEN)の方が優先的に見られてしまうようです。

https://scrapbox.io/tsugitta/GitHub_Actions_の_checkout@v2_と_access_token_認証を合わせた結果ハマった

終わりに

以上がGitHub上にpushしたソースをパッケージとして使う方法でした。基本的にリポジトリはビルド前のコードが置いてあるのでパッケージとして使いづらいかなと思っていましたが、gh-pages用のActionsを上手く使い回すことでビルドしたコードも簡単にpushすることができ、npm publishするのとそこまで変わらない手間で済みそうでした。利用者側はGitHubリポジトリにアクセスできればプライベートであっても普通にinstallすることができて、使い勝手も良さそうです。
GitHub Packagesも検討していましたが、そちらは残念ながらSSH認証ができず手元でもトークンの設定など一手間が必要で断念しました。この辺が解消されたらGitHub Packagesもありですがひとまずはリポジトリを直接参照するやり方でいこうかなと思っています。

Discussion