Typescriptで独自アクション作る
以前GithubActionsを使ってHomebrewのリリースを自動化し、一部のstepを独自アクションとして切り出した。
www.rasukarusan.com www.rasukarusan.com
前回のDockerで書き出したアクションを、Typescriptで書いてみる。
対象のstep
steps: # 1つ前のtagからの差分を取得 - name: Get commit summary id: get_commit_summary run: | PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n 2p) COMMIT_SUMMARY="$(git log --oneline --pretty=tformat:"%h %s" $PREVIOUS_TAG..${{ github.ref }})" COMMIT_SUMMARY="${COMMIT_SUMMARY//$'\n'/'%0A'}" echo ::set-output name=COMMIT_SUMMARY::$COMMIT_SUMMARY
1つ前のtagからの差分を取得する、というstep。リリースノートに記載するためのもの。
作り方
基本的に公式のJavascriptでの独自アクションの作り方と、Typescriptで書かれたテンプレートのリポジトリを見ながらやればOK。
完成形
~/Documents/github/gitblamer/.github/actions/commit-summary $ tree . ├── action.yml ├── package.json ├── src │ └── index.ts └── tsconfig.json
諸々不要なものを削った最小構成。npm
でインストールするものは下記。
npm init -y npm install @actions/core npm install @actions/exec npm install --save-dev @vercel/ncc npm install --save-dev @types/node npm install --save-dev typescript
@actions/core
: 引数受け取ったりecho ::set-output name=
みたいなことをするため。@actions/exec
: git等のコマンドを実行するため@vercel/ncc
: Node.jsモジュールコンパイル君@types/node
: typescript使うためtypescript
: typescript使うため
action.yml
name: 'Get Commit Summary' description: 'Get commits from previrous tag to new tag' inputs: ref: description: 'new tag' required: true default: 'please set ${{ github.ref }}' outputs: summary: description: 'commit summary' runs: using: 'node12' main: 'dist/index.js'
Dockerで作ったときとほぼ一緒。違うところはruns
の箇所。
runs: - using: 'docker' - image: 'Dockerfile' - args: - - ${{ inputs.ref }} + using: 'node12' + main: 'dist/index.js'
package.json
{ "name": "commit-summary", "version": "1.0.0", "main": "dist/index.js", "description": "", "scripts": { "build": "ncc build src/index.ts -o dist --license licenses.txt" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "@actions/core": "^1.2.6", "@actions/exec": "^1.0.4" }, "devDependencies": { "@types/node": "^14.14.22", "@vercel/ncc": "^0.27.0", "typescript": "^4.1.3" } }
tsconfig
{ "compilerOptions": { "target": "es6", "module": "commonjs" } }
公式だともうちょっと色々書いてあったけど、必要最小限にした。
src/index.ts
import * as core from '@actions/core' import * as exec from '@actions/exec' import { ExecOptions } from '@actions/exec' const execute = async (command: string): Promise<string> => { let output = '' const options: ExecOptions = {} options.listeners = { stdout: (data: Buffer) => { output += data.toString() }, stderr: (data: Buffer) => { console.error(data) } } await exec.exec(command, null, options) return output } const main = async () => { try { const newTag = core.getInput('ref') const preTag = await execute('/bin/bash -c "git tag --sort=-creatordate | sed -n 2p"') const summary = await execute(`git log --oneline --pretty=tformat:"%h %s" ${preTag.trim()}..${newTag}`) core.setOutput("summary", summary) } catch (error) { core.setFailed(error.message) } } main()
アクションの肝。基本的にruns
で実行していたコマンドを、exec.exec()
で実行していけばいい。
が、exec.exec()
はconst result = await exec.exec('ls')
のようにしても標準出力の内容が受け取れない。返ってくるのは0
や1
などの終了コードのみ。標準出力を受け取りたい場合、exec.exec()
の第三引数のoptionsに、コールバックを受け取るlistenersを登録するなど、ごにょごにょしないといけない。
// ごにょごにょして標準出力を返すようにする const execute = async (command: string): Promise<string> => { let output = '' const options: ExecOptions = {} options.listeners = { stdout: (data: Buffer) => { output += data.toString() }, stderr: (data: Buffer) => { console.error(data) } } await exec.exec(command, null, options) return output }
また、exec.exec()
はパイプの実行に対応していない。下記の書き方空文字が返ってきてしまう。
const preTag = await execute('git tag --sort=-creatordate | sed -n 2p')
パイプで実行したい場合、/bin/bash -c
で実行する必要がある。
const preTag = await execute('/bin/bash -c "git tag --sort=-creatordate | sed -n 2p"')
もしくはせっかくtsで書いているので、パイプ以降でやりたい処理をtsで書いてもいい。今回の場合やりたいことは「上から二行目を取得する」なのでpreTag.split('\n')[1]
みたいな感じでもいい。
ビルドしてworkflows/release.ymlを更新したら終了
npm run build
ビルド後のディレクトリ構造
~/Documents/github/gitblamer/.github/actions/commit-summary $ tree . ├── action.yml ├── dist │ ├── index.js │ └── licenses.txt ├── package-lock.json ├── package.json ├── src │ └── index.ts └── tsconfig.json 2 directories, 7 files
workflows/release.yml
# 1つ前のtagからの差分を取得 - name: Get commit summary id: get_commit_summary - run: | - PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n 2p) - COMMIT_SUMMARY="$(git log --oneline --pretty=tformat:"%h %s" $PREVIOUS_TAG..${{ github.ref }})" - COMMIT_SUMMARY="${COMMIT_SUMMARY//$'\n'/'%0A'}" - echo ::set-output name=COMMIT_SUMMARY::$COMMIT_SUMMARY + uses: ./.github/actions/commit-summary + with: + ref: ${{ github.ref }}
すっきり。
終わり
Docker、Typescriptの両方でアクションを作成することができた。今回みたいなちょっとしたスクリプトレベルだったらTypescriptじゃなくていい。
もうちょっとヘビーな処理とかチーム開発だったら、絶対Typescript選びたくなるんでしょうね。