Azure Pipelines職人(自称)ですが、そのAzure PipelinesのクローンであるGitHub Actionsでも職人(自称)をやらないとなぁ~~となったのでActionを作って公開してみましたので、Actionの作り方など
前置き
GitHub ActionsやActionの作り方などは公式ドキュメントにも書かれてますので1次ソースとしてどうぞ
作り方を決める
GitHub ActionsではAzure Pipelinesと同様にWindows, Linux, macOSで動作します。そしてActionの作り方は表のように2つあります
作り方 | 動作環境 |
---|---|
Docker Container | Linux |
JavaScript(Node.js) | Linux, Windows, macOS |
Docker Containerに関してはAzure Pipelinesでの対応状況を見る感じそのうちWindowsにも対応しそうですが、現在のところではLinuxでしか動かないみたいです
今回はWindowsやmacOSにも対応させたいのでJavaScript(Node.js)のほうで作ろうと思います。そしてJavaScriptは書きたくないのでTypeScriptを使います
あと、今回はDangerを実行するactionを作ります
リポジトリーセットアップ
Node.jsのv12系統を使うので公式ページなどから最新版をインストールしてください。あとVisual Studio Codeのインストールも忘れずに
ローカルにリポジトリーとなるフォルダーを作成したら、VS Codeで開いてセットアップをしていきます
npm init -y
npm install typescript --save-dev
(typescriptがグローバルになければnpm install -g typescript
も)tsc -init
npm install @types/node --save-dev
npm install @actions/core --save
npm install @actions/io --save
npm install @actions/exec --save
@actions/core
とかはGitHub Actions用に用意されているパッケージでactions/toolkitに見に行けば使い方などが書かれてます。今回はRubyとBundlerが入ってるかを確認するために@actions/io
、コマンドの実行のために@actions/exec
も入れます
また、tsconfig.json
ではoutDir
を./lib
に、rootDir
を./src
にしておいてください
実行コードを書く
src/main.ts
ファイルを作り、そこにactionの実行コードを書いていきます
import * as core from "@actions/core"; async function run() { try { } catch (error) { core.setFailed(error.message); } } run();
基本の形としてrun関数を書いておき、その中のtry-catchのtry部分に処理を書いていくことにします
import * as io from "@actions/io"; async function checkEnvironment() { await io.which("ruby", true); await io.which("bundle", true); }
まず、RubyとBundlerの存在確認ですが、@actions/io
のwhich
関数の第2引数をtrue
にしておくとコマンドが存在しなかった場合にエラーを排出するようなのでこれを利用します
interface Option { readonly dangerVersion: string; readonly pluginsFile: string | null; readonly dangerFile: string; readonly dangerId: string; } async function getOption(): Promise<Option> { return { dangerVersion: core.getInput("danger_version", { required: true }), pluginsFile: core.getInput("plugins_file"), dangerFile: core.getInput("danger_file", { required: true }), dangerId: core.getInput("danger_id", { required: true }) }; }
次にactionに渡される引数を取得する部分を書きます。@actions/core
にgetInput
関数があるのでそれを使っていきましょう
今回はDangerを実行するだけのactionなので、Dangerのバージョン情報とDangerのpluginをインストールするためのgemfileのパスとDangerを実行するための情報を引数から取得します
import * as exec from "@actions/exec"; async function installDanger(option: Option) { await exec.exec( `gem install danger --version "${option.dangerVersion}"`, undefined, { failOnStdErr: true } ); if (option.pluginsFile != null) { await exec.exec( `bundle install --gemfile=${option.pluginsFile}`, undefined, { failOnStdErr: true } ); } }
そして@actions/exec
のexec
関数を利用してDangerのインストールやDanger pluginのインストールを行っていきます。第3引数でexec
関数のオプションを渡せるので、failOnStdErr
をtrue
にしておきます
async function runDanger(option: Option) { await exec.exec( `danger --dangerfile=${option.dangerFile} --danger_id=${option.dangerId}`, undefined, { failOnStdErr: true } ); }
ここまで来るとあとはただDangerを実行するだけのコードを書けばいいだけです
async function ignoreRubyWarning() { await core.exportVariable("RUBYOPT", "-W0"); }
ただ、これを実行してみるとRubyがおせっかいにもwarning: Insecure world writable dir {RubyBinPath} in PATH, mode 040777
を排出してきて、actionが失敗した状態になってしまうので、Rubyの警告を出さないようにしておきます
あとはこれらの関数を先ほどのrun
関数で繋げていけばいいだけです
完成コード: src/main.ts
action.ymlを書く
あとはリポジトリートップにaction.yml
を書くだけです
name: 'Danger action' description: 'Run danger, unofficial action' author: 'MeilCli' branding: icon: zap color: red inputs: danger_version: description: 'danger version' default: '>= 6.0.0' plugins_file: description: 'gemfile for danger plugin' danger_file: description: 'dangerfile for danger' required: true danger_id: description: 'danger id, set identifier string' required: true runs: using: 'node12' main: 'lib/main.js'
今回作成したactionのaction.yml
はこのようになっています
inputs
に引数の情報を書いていき、runs
でNode.jsを使うことと実行ファイルを指定してください
作ったactionはMarketplaceに公開するためにbranding
とか書いてますがリポジトリーを公開するだけなら必要ないはずです
公開する
公開するにはGitHubのリポジトリーに置いておく必要があります
tsc
コマンドなどでTypeScriptをトランスコンパイルしておきlib/main.js
があることを確認します。確認出来たらpushしましょう
注意点として、実行ファイルが依存しているnode_modules
もリポジトリー内に配置しておく必要があります
node_modules/* !node_modules/@actions
今回は必要最低限にとどめるために.gitignore
にこのように書きました。またRelease Tagを作っておくと便利です(v1とかv2のような感じのもの)
利用する
jobs: danger: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-ruby@v1 with: ruby-version: '2.6' - uses: MeilCli/danger-action@v1 with: plugins_file: 'Gemfile' danger_file: 'Dangerfile' danger_id: 'danger-pr' env: DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
利用するときは通常のactionを利用するように{アカウント名}/{リポジトリー名}@{バージョン}
を書きます
実はactions/checkout
などはactionsのOrganizationで公開されていて、usesの部分で特殊対応が入ってるわけではありません(action.ymlのところで特殊対応はあったりしますが)
おわりに
というわけで、GitHub ActionsのActionの作り方の流れはわかってもらえたかなと思います。TypeScriptを使えば比較的簡単にActionを作ることができるので、皆さんもどんどん作っていって、どんどん便利なものができていけばなと思います
今回作ったもの:
Marketplaceにも公開してます: