ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

ChatGPTをターミナルからサクッと試す

ChatGPTをターミナルからサクッと試す方法を紹介します。
アカウント登録が必要なもののクレジットカードの登録などはないので5分もあれば試すことができました。

※ 2022年12月5日時点の情報です。AI周りは情報が速いのであくまで当時の情報ということを念頭に進めていただければ。

アカウント登録

https://openai.com/api/

API_KEY発行

https://beta.openai.com/account/api-keys

ドキュメント

https://beta.openai.com/docs/introduction/overview

無料期間について

$18分のクレジットが付与されます。ただし有効期限は3ヶ月間です。
それ以降は有料となりますが、クレジットカードを登録しなくても$18分は付与されるので、勝手に課金されるなどはなさそうです。

価格に関してはこちらと、ドキュメント内に詳細が書かれていました。

リクエスト数ではなくトークン、つまり何単語処理したかで課金額が決定されるみたいです。

100回以上いろんなAPI試していますが、まだ$1にもなっていないので結構潤沢に付与されていると思ってよさそうです。

 

ChatAPIを試す

curl -s https://api.openai.com/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $YOUR_API_KEY"  \
-d '{
  "model": "text-davinci-003",
  "prompt": "コメダコーヒー美味しいよね",
  "temperature": 0.9, 
  "max_tokens": 150,
  "top_p": 1,
  "presence_penalty": 0.6,
  "frequency_penalty": 0.0,
  "stop": [" Human:"," AI:"]
}'

「コメダコーヒー美味しいよね」という問いに対してしっかりと回答してくれてます。すごい。

画像生成APIを試す

キーワードは日本語も指定可能みたいです。

curl https://api.openai.com/v1/images/generations  \
-H 'Content-Type: application/json'  \
-H "Authorization: Bearer $YOUR_API_KEY"  \
-d '{
    "prompt": "かわいい犬",
    "n": 2,
    "size": "256x256"
}'

レスポンス

{
  "created": 1670219524,
  "data": [
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/private/org-6Hi5lrdCx5dsXH0mjXtyVwaE/user-DX3s1xevY1GoZvdE2IXP2Id9/img-H5UUDrDig7EgXFTVUeHiKzSc.png?st=2022-12-05T04%3A52%3A04Z&se=2022-12-05T06%3A52%3A04Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2022-12-05T00%3A43%3A30Z&ske=2022-12-06T00%3A43%3A30Z&sks=b&skv=2021-08-06&sig=yi2ooG1NvVCafsVjAObjhWNnHDBEeq%2By9MgGYSUNLqU%3D"
    },
    {
      "url": "https://oaidalleapiprodscus.blob.core.windows.net/private/org-6Hi5lrdCx5dsXH0mjXtyVwaE/user-DX3s1xevY1GoZvdE2IXP2Id9/img-Pr4nvZSvwLfCvegDbN602iGm.png?st=2022-12-05T04%3A52%3A04Z&se=2022-12-05T06%3A52%3A04Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2022-12-05T00%3A43%3A30Z&ske=2022-12-06T00%3A43%3A30Z&sks=b&skv=2021-08-06&sig=txCCiddT9q51bCJj%2BFXLX1vHzF6nZps27gyeKZhO7xg%3D"
    }
  ]
}

柴犬の画像が生成されました

他のAPIの試し方

https://beta.openai.com/examples

上記のページから試したいAPIをクリックします。

 

ダイアログが表示されるので下の方にスクロールし、「API Request」の言語をcurlにすればリクエスト文がコピーできます。

シンプルでわかりやすいですね

終わりに

ChatGPT、APIが用意されているのでサクッと試せていいですね!回答や要約の精度もかなり高くてAIもここまで来たのか〜という感じです。
アプリも作れたというツイートもあり、いやほんとにすごいですね。今後も続報追っていきたいと思います。

zshが遅いのはvcs_infoが原因だったので高速化した

zshの起動が遅い。tmuxでパネル分割したときやls打ったあとがもっさりしてきた。
自分のzsh環境はoh-my-zshやpreztoなどは使っていないプレーンなzsh環境だったが、PROMPT表示に用いるvcs_infoが遅かったので改善した。

vcs_info

gitやsubversionなどのバージョン管理システムから情報を取得してくれる関数。プロンプトにブランチ名や状態を表示するのに利用する。

リポジトリ情報の表示。ブランチ名の表示と差分があると✗がつく。

vcs_infoを使ったリポジトリ情報の表示は.zshrcに下記のような記載をするだけだ。

# PROMPTテーマ
setopt prompt_subst #プロンプト表示する度に変数を展開
local CYAN=$'%{\e[36m%}'
local GRAY=$'%{\e[37m%}'

autoload -Uz vcs_info
zstyle ':vcs_info:git:*' check-for-changes true    # formats 設定項目で %c,%u が使用可
zstyle ':vcs_info:git:*' stagedstr "%F{red}"     # commit されていないファイルがある
zstyle ':vcs_info:git:*' unstagedstr "%F{red}"     # add されていないファイルがある
zstyle ':vcs_info:*' formats "%F{green}%b %c%u%m %f" # 通常
zstyle ':vcs_info:*' actionformats '[%b|%a]'     # rebase 途中,merge コンフリクト等 formats 外の表示
precmd () { 
  vcs_info
}
PROMPT="
${CYAN}%~%f"
PROMPT=$PROMPT'  ${vcs_info_msg_0_}
${GRAY}$ %f'

zstyle ':vcs_info:git+set-message:*' hooks git-is_clean git-untracked
# 状態がクリーンか判定
function +vi-git-is_clean(){
  if [ -z "$(git status --short 2>/dev/null)" ];then
    hook_com[misc]+=""
  fi
}
# unstaged, untrackedの検知
function +vi-git-untracked() {
  if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
    hook_com[unstaged]+='%F{red}✗%f'
  fi
}

precmd()内にvcs_infoを書くことでPROMPT表示のたびに呼び出される。

vcs_infoが遅いのでやめる

vcs_infoは様々な情報を取得するため、自分にとっては余分な処理も走っている。自分の環境において必要なのはブランチ名、diffの有無だけだったのでgitコマンドのみにした。

# PROMPTテーマ
setopt prompt_subst #プロンプト表示する度に変数を展開

precmd () { 
  if [ -n "$(git status --short 2>/dev/null)" ];then
    export GIT_HAS_DIFF=""
    export GIT_NON_DIFF=""
  else 
    export GIT_HAS_DIFF=""
    export GIT_NON_DIFF=""
  fi
  # git管理されているか確認
  git status --porcelain >/dev/null 2>&1
  if [ $? -ne 0 ];then
    export GIT_HAS_DIFF=""
    export GIT_NON_DIFF=""
  fi
  export BRANCH_NAME=$(git branch --show-current 2>/dev/null)
}
# 末尾に空白をつけることで改行される
PROMPT=" 
%F{cyan}%~%f"
PROMPT=${PROMPT}'%F{green}  ${BRANCH_NAME} ${GIT_NON_DIFF}%F{red}${GIT_HAS_DIFF} 
%f$ '

precmd()内でvcs_infoの代わりにgitコマンドを実行し、変数をEXPORTする。EXPORTされた変数をPROMPT内で利用する形。 ちゃんとした計測はしていないが、体感300msぐらいは早くなった気がする。特にM1 MacではないIntel MacBookProでは顕著に効果を実感できた。

終わり

たまに素のzshにするとその速さにびびる。

shellだけでリッチなTUIアプリが作れる「gum」がすごい

 

Shell ScriptだけでリッチなTUIアプリが作れるようになる「gum」がかなり良い感じ。

github.com

入力、書き込み、選択、Fuzzy、ローディングなど、TUIアプリに必要なものがサクッと書けるようになる。

インストール

go install github.com/charmbracelet/gum@latest

機能

  • Input
  • Write
  • Filter
  • Choose
  • Confirm
  • Spin

Input

gum input --placeholder "input here"

Write

gum write --placeholder "Details of this change"

Ctrl+dで入力終了

Filter

ls -1 | gum filter

fzfでおなじみのFuzzy Filter。fzfのようにpreview表示などはできないっぽい。

Choose

ls -1 | gum choose --limit 3

xまたはSPACEで選択。

Confirm

gum confirm && echo "Hi" || echo "Bye"

Spin

gum spin --spinner dot --title "Wait..." -- sleep 2

いわゆるローディング。

--spinnerにはdotの他に

  • line
  • minidot
  • jump
  • pulse
  • points
  • globe
  • moon
  • monkey
  • meter
  • hamburger

が指定できる。

Format, Style, Layout

文字をBoldにしたり枠線を作ったりなどのスタイル周りを調整する機能もある。 ただStyleやLayoutは自分の環境だとズレて表示されてしまったりした。

gum style --padding "1 5" --border double --border-foreground 212 "I"

borderがズレてしまう

終わりに

いままでfzfreadmktempなどを使ってフィルタリングや書き込みを行っていたところを、gum一本で完結できるのが良い。
gumの他にもいろいろなTUIツールを作っているCharmという団体を初めて知った。

slack api curlまとめ

 

まとめ

github.com

チャンネルのメッセージを取得

https://api.slack.com/methods/conversations.history

curl -X GET -H "Authorization: Bearer ${TOKEN}" \
https://slack.com/api/conversations.history?channel=${CHANNEL}

チャンネルIDの取得

ただしスレッド内のメッセージは取得できない。スレッド内のメッセージを取得したい場合は次のconversations.repliesを利用する。

スレッド内のメッセージを取得

https://api.slack.com/methods/conversations.replies

curl -X GET -H "Authorization: Bearer ${TOKEN}" \
"https://slack.com/api/conversations.replies?ts=${ts}&channel=${CHANNEL}"

tsは該当スレッドの「リンクをコピー」し、末尾から6桁目にコンマを打った数字。

例: https://yourcompany.slack.com/archives/C02QYEXA8SW/p1655885927900929
の場合、1655885927.900929tsとなる。

メッセージ送信

https://api.slack.com/methods/chat.postMessage

curl -X POST  -H "Authorization: Bearer ${TOKEN}" \
-F channel=${CHANNEL} -F text="Hello World" \
https://slack.com/api/chat.postMessage

メッセージ送信(スレッドにつなげる場合)

curl -X POST  -H "Authorization: Bearer ${TOKEN}" \
-F channel=${CHANNEL} -F text="Hello World" -F thread_ts="$ts" \
https://slack.com/api/chat.postMessage

メッセージ送信(メンションやコードブロックなど凝った文章の場合)

blocks=$(cat <<EOS
Hello :smile:
\`\`\`
this is code blocks
\`\`\`
EOS
)

curl -X POST  -H "Authorization: Bearer ${TOKEN}" \
-F channel=${CHANNEL} -F thread_ts="$ts" -F blocks="[{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"$blocks\"}}]" \
https://slack.com/api/chat.postMessage

api.slack.com

メッセージ削除

https://api.slack.com/methods/chat.delete

curl -X POST -H "Authorization: Bearer ${TOKEN}" \
-F channel=${CHANNEL} -F ts=${ts} \
https://slack.com/api/chat.delete

ファイル送信

https://api.slack.com/methods/files.upload

curl -X POST -H "Authorization: Bearer ${TOKEN}" \
-F channels=${CHANNEL} -F file=@hoge.png -F thread_ts="${ts}" \
https://slack.com/api/files.upload

今月のコミット一覧を出す

GithubでsinceuntilをURLにつければ検索できるんだな、知らんかった。

https://github.com/Rasukarusan/dotfiles/commits?author=Rasukarusan&since=2021-11-01&until=2021-11-30

f:id:rasukarusan:20211128162805p:plain
コマンドだとこう

git log --after '2021/11/01' --before '2021/11/30'

f:id:rasukarusan:20211225144046p:plain:w400

差分内容も出したいときはこう

git diff $(git log --after '2021/11/01' --before '2021/11/30' --pretty="%H" | sed -n '1p;$p' | tr '\n' " ")

f:id:rasukarusan:20211225144130p:plain:w400
日付間の変更行数を取得したいときはこう

git diff --stat @{2021-11-01}..@{2021-11-30}

f:id:rasukarusan:20211225143942p:plain:w400
日付間の変更行数

今月の振り返りに使えそう

Mac専用sipsコマンドで、画像に枠線をサクッとつける

f:id:rasukarusan:20210707141514p:plain
Mac標準搭載のコマンドでsipsコマンドがある。画像情報を取得したり付与したりできる。
画像のコマンドといえばimagemagickだが、imagemagickよりも少ないオプションで、かつ直感的に実行できるのがsipsコマンドの良いところ。brew等で別途インストールしなくて済むのも良い。

sipsコマンドを知る上でめちゃくちゃ参考になるサイト

基本的な使い方はここ。

Macのターミナルで簡単に画像処理できるsipsの使い方 - Qiita

こちらはOCRツールのtesseractとsipsを組み合わせて中々面白いことをしている。

Tesseractでデスクトップの一部にOCRをかける - Qiita

sipsで画像に枠線をつける

sips -p ${height} ${width} --padColor ${color} ${imagePath} -o ${newImagePath}

# 元画像320x320, 枠線の太さを20に設定
sips -p 340 340 --padColor 000000 demo.png -o border_demo.png

f:id:rasukarusan:20210707140627j:plain
元画像

f:id:rasukarusan:20210707140637j:plain
#000000の枠線太さ20の画像

元画像のサイズを知っておく必要があるので、それもsipsコマンドで取得するようにすると汎用的になる。
sipsコマンドで画像の幅、高さを指定するには-gオプションを利用する

# 幅を取得
sips -g pixelWidth demo.jpg

# 高さを取得
sips -g pixelHeight demo.jpg

add_border.sh

#!/usr/bin/env bash

main() {
  local image=$1
  local color=${2:-a0a8a9}
  local borderWeight=${3:-10}
  local width=$(sips -g pixelWidth $1 | awk -F ' ' '{print $2}')
  local height=$(sips -g pixelHeight $1 | awk -F ' ' '{print $2}')
  local borderWidth=$(expr $width + $borderWeight)
  local borderHeight=$(expr $height + $borderWeight) 
  sips -p $borderHeight $borderWidth --padColor $color $image -o border_${image}
}
main

使い方

sh add_border.sh demo.jpg

# 枠線色#000000、太さ20
sh add_border.sh demo.jpg 000000 20

終わり

地味に枠線ほしいときあるから、きっと役に立つ。

Sequel AceをApplescriptで操作する

f:id:rasukarusan:20210627222634p:plain
 

以前Sequel ProをApplescriptで操作するのはやっていたが、昨今はSequel Aceを使うようになった。
Sequel ProとAceではFavorite.plistのPATHやUIの配置などがいくつか異なっていたため、それをまとめたい。

スクリプト

まずどういったスクリプトなのかを載せておく。お気に入りリストを取得してfzfで選択し、接続するというスクリプト。

GIFはSequel Proだが同じことがSequel Aceでも可能

ソース

#!/bin/sh

#
# Sequel Aceで指定した接続を開く
# 引数にはSequelProの「お気に入り」の行番号を示すインデックスが入る
#
function run_sequel_pro() {
local app='Sequel Ace'
osascript -- - "$@" << EOF
on run argv
tell application "${app}"
    activate
    delay 0.5
    tell application "System Events"
        tell process "${app}"
            set frontmost to true
            delay 0.5
            repeat with i from 1 to (count argv)
                keystroke "t" using {command down}
                tell window "${app}"
                    delay 0.5
                    tell outline 1 of scroll area 1 of splitter group 1 of window "${app}" of application process "${app}" of application "System Events"
                        # row 1は「クイック接続」、row 2は「お気に入り」の行なので実質一番上はrow 3となる
                        set _row_index to (item i of argv as number) + 2
                        select row _row_index
                    end tell
                    tell scroll area 2 of splitter group 1 of window "${app}" of application process "${app}" of application "System Events"
                        click button "Connect"
                    end tell
                end tell
            end repeat
        end tell
    end tell
end tell
end run
EOF
}

function main() {
  local favorites=$(plutil -convert json ~/Library/Containers/com.sequel-ace.sequel-ace/Data/Library/Application\ Support/Sequel\ Ace/Data/Favorites.plist -o - | jq -r '."Favorites Root".Children[].name')
    local targets=($(echo "${favorites}" | fzf))
    local rows=()
    for target in ${targets[@]}; do
        echo $target
        local row=$(echo "${favorites}" | grep -n ${target} | cut -d ':' -f 1)
        rows=(${rows[@]} $row)
    done
    [ ${#rows[@]} -eq 0 ] && return 130
    run_sequel_pro ${rows[@]} >/dev/null
}

main

Sequel Pro と Ace で変わったこと

  • Favorite.plistのPATH
  • UI Elementの指定方法

Favorite.plistのPATH

お気に入り一覧はFavorite.plistというファイルに載っている。Sequel Proでは

~/Library/Application\ Support/Sequel\ Pro/Data/Favorites.plist

にあったが、Sequel Aceでは下記のPATHに変わっている。

~/Library/Containers/com.sequel-ace.sequel-ace/Data/Library/Application\ Support/Sequel\ Ace/Data/Favorites.plist 

これは「Sequel Ace | MySQL/MariaDB database management for macOS」にも載っている。
ちなみにパスワードが知りたい場合はキーチェーンに保存されている。キーチェーンを開いて検索で「sequel」と打てば出てくるはず。

UI Elements

Sequel Proではお気に入りリストの行を選択するのに下記のようにアクセスしていた。

tell outline 1 of scroll area 1 of splitter group 1 of group 2 of window "Sequel Pro" of application process "Sequel Pro" of application "System Events"

Sequel Aceではgroup 2 ofがなくなっていた。

- tell outline 1 of scroll area 1 of splitter group 1 of group 2 of window "Sequel Pro" of application process "Sequel Pro" of application "System Events"
+ tell outline 1 of scroll area 1 of splitter group 1 of window "Sequel Ace" of application process "Sequel Ace" of application "System Events"

終わり

以上。Sequel AceもApplescriptで操作できてよかった。

gif動画から指定のフレーム(最初・最後・最後から2番目など)を抜き出す

f:id:rasukarusan:20210418171557p:plain

convertコマンドで可能。とりあえずbrewでインストールしましょう。

# imagemagickをインストールするとconvertコマンドが使えるようになる
brew install imagemagick

指定のフレームを抜き出す

最初

convert 'neko.gif[0]' first.png

最後

convert 'neko.gif[-1]' last.png

最後から2番目

convert 'neko.gif[-2]' pre-last.png

終わり

gif動画の始まりと終わりを区別するために、最初と最後のフレームにSTARTENDの文字列を合成しようと思って調べたら出てきた。たぶん文字列の合成もconvertコマンドで出来るから、ImageMagickマジ便利。

今更ながらgit-ftp便利すぎた

f:id:rasukarusan:20210226164209p:plain
とりあえず必要なことをババっと書いておく

インストール

brew install git-ftp

もしくは、リポジトリにgit-ftpの実行ファイルがあるからcloneしてきて使ってもいい。

git clone https://github.com/git-ftp/git-ftp
cd git-ftp
./git-ftp

初期設定

git config git-ftp.url "ftp://FTPホスト/指定ディレクトリ"
git config git-ftp.user "FTPユーザー名"
git config git-ftp.password "FTPパスワード"
git config git-ftp.syncroot pushしたいディレクトリ

ignore設定

.git-ftp-ignoreに書き込んでいく。書き方は.gitignoreと同じ。

touch .git-ftp-ignore

サーバーにPUSH

git ftp init

初回のみinitが必要で、initをしたら自動的にpushされるので注意。
二度目以降はgit ftp pushでpushする。

git ftp push

staging, production環境で分けたい

git-ftp.<environment>で振り分け可能。

staging

git config git-ftp.stg.url "ftp://ステージングのFTPホスト/指定ディレクリ"
git config git-ftp.stg.user "ステージングのFTPユーザー"
git config git-ftp.stg.password "パスワード"
git config git-ftp.stg.syncroot pushしたいディレクトリ

production

git config git-ftp.prd.url "ftp://プロダクションのFTPホスト/指定ディレクリ"
git config git-ftp.prd.user "プロダクションのFTPユーザー"
git config git-ftp.prd.password "パスワード"
git config git-ftp.prd.syncroot pushしたいディレクトリ

確認

cat .git/config

staging, production環境で分けてPUSHする

-sで指定する

staging

git ftp init -s stg
git ftp push -s stg

production

git ftp init -s prd
git ftp push -s prd

ちなみに振り分け設定していない場合に、-sをつけずにinit等をしてもエラーとなる。

$ git ftp init
fatal: Remote host not set.

終わり

急遽Wordpressをすることになり、自前で用意したステージング用サーバーがFTPのみ利用可能で必要になった。
Wordpressのデプロイにはwordmoveが最適っぽいが、設定ファイルを書くのがだるすぎたので却下した。開発用サーバーとかはどうなってもいいから楽なやつがいい。

ターミナル上にwifi接続のQRを表示できるsdushantha/wifi-passwordがおもしろい

f:id:rasukarusan:20210128170048p:plain
github.com

今接続しているwifiのパスワード、またはQRコードをTermianl上に表示できるツール。
使用頻度は少ないかもしれないが、あったら地味に便利。

インストールはpipで可能。

python3 -m pip install --user wifi-password

実行

wifi-password --qrcode

f:id:rasukarusan:20210128154847p:plain:w500
実行するとキーチェーンの許可が求められる

ユーザー名、パスワードを入力するとQRコードがターミナル上に表示される。

f:id:rasukarusan:20210128154904j:plain:w500
なんとなく読み取られるのがアレなためモザイクをかけているが、実際はきれいなQRコードが表示される

このQRをiPhoneなどのカメラで読み取れば接続できる。
オプションで--imageをつければ画像として保存することも可能。ただもしかしたらpipで依存モジュールをインストールする必要があるかも。

元祖rauchg/wifi-passwordの拡張版

rauchg/wifi-passwordが元となっているツール。
Windows, Linux, Macのマルチプラットフォームに対応したのと、QRコードの機能追加されているのが今回のもの。 また、元祖はShellScriptで書かれているが、sdushantha/wifi-passwordはPythonで書かれている。ただ、パスワード取得の処理などはどちらも同じ。

どうやってパスワードを取得しているのか

macのsecurityコマンドを実行して取得している。

security find-generic-password -l ${ssid} -D 'AirPort network password' -w

securityコマンドについては下記2つの記事がわかりやすかった。あとman securityに色々書かれているので見ると楽しい。 yukidarake.hateblo.jp macromates.com

終わり

securityコマンドの存在を初めて知った。
今までトークンなどそれ用のファイルに書き込んで、shellなどから読み取っていたが、キーチェーンに登録すればファイルとして残さなくて済みそう。

Githhub Actionsをローカルで実行するnectos/actでcommand not foundが出たときの対処法

f:id:rasukarusan:20210127224510p:plain:w600
Github Actionsをローカル実行できるツールnektos/actの話。

act -P でimageを指定するも必ずnode:12.6-buster-slimで実行されてしまう

actの実行時の環境は、デフォルトではnode:12.6-buster-slimが選択されるが、このimageは最小限の構成なのでgitコマンド等が入っていない。 なので下記のようにcommand not foundと出てしまい、実行に失敗する。

$ act
WARN[0000] unable to get git repo: section "remote \"origin\"" does not exist
[Test Workflow/Run Git Commands] 🚀  Start image=node:12.6-buster-slim
...略
[Test Workflow/Run Git Commands] ⭐  Run execute command!
| /github/workflow/set_value_for_formula: line 1: git: command not found
[Test Workflow/Run Git Commands]   ❌  Failure - execute command!
Error: exit with `FAILURE`: 127

しかしactにはオプションが用意されていてact -Pで実行するimageが選択できる。

act -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04

ただ上記のコマンドを実行しても選択されるimageがnode:12.6-buster-slimのままになってしまう。

f:id:rasukarusan:20210128010718p:plain
nektos/act-environments-ubuntu:18.04を指定しているのにnode:12.6-buster-slimが選択される

これを解決する。

原因:yamlのruns-on:ubuntu-latestとact -P ubuntu-18.04=...がちぐはぐだったから

.github/workflows/test-workflow.yml

name: Test Workflow
on:
  push:
    tags:
      - 'v*'
jobs:
  build:
    name: Run Git Commands
    runs-on: ubuntu-latest
    steps:
      - name: execute command!
        id: set_value_for_formula
        run: |
          git status

actコマンド

act -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04

yamlファイルはruns-on: ubuntu-latestなのにコマンドがact -P ubuntu-18.04=...になっているとnode:12.6-buster-slimが選択されてしまう。

対処法

ymlファイルとコマンド実行をちゃんと合わせる。

- act -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04
+ act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04

公式のREADMEだとubuntu-18.04で書かれているため、そのままコピペするとハマってしまうかもしれない。

確認

f:id:rasukarusan:20210128011028p:plain

ちゃんと🚀 Start image=nektos/act-environments-ubuntu:18.04になっていますね。

Tips

actの実行前にdocker pull nektos/act-environments-ubuntu:18.04しておいたほうがいい

docker pullをせずにいきなりact -P ubuntu-latest=nektos/act-environments-ubuntu:18.04を実行しても問題ないが、初回実行時はimageをpullしてくるのでめちゃくちゃ時間がかかる上に進捗が見えないので止まったように見えてしまう。
docker pullなら進捗が見えるので、先にやっておいたほうが精神上良い。

docker pull nektos/act-environments-ubuntu:18.04

yamlのruns-onubuntu-18.04だったらgitコマンドが使えるnode:12-busterが選択される

jobs:
  build:
    name: Run Git Commands
    runs-on: ubuntu-18.04

としておけばactで実行するときslimよりちょっとリッチなnode:12-busterで実行される。

f:id:rasukarusan:20210127223032p:plain
gitコマンドが使えるぐらいにはリッチなnode:12-busterが選択される

READMEにはnode:12.6-buster-slimと書かれていたので、自分が間違っているのか更新し忘れなのかわからないが、一応プルリクを出しておいた。結果はまたここに書く。

github.com

Dockerのバージョンを上げないとそもそもactが実行できない

下記のエラーが出る場合、Dockerのバージョンを上げたら実行できるようになる。

$ act
[Test Workflow/printInputs] 🚀  Start image=node:12.6-buster-slim
[Test Workflow/printInputs]   🐳  docker run image=node:12.6-buster-slim entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Test Workflow/printInputs]   🐳  docker cp src=/Users/tanakanaoto/Documents/github/gitblamer/. dst=/github/workspace
Error: error during connect: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.40/exec/dd9940466880af832e8b93ba9d2fbde450785bfac24ae2a73dd2cbd99aec5ce2/start": net/http: HTTP/1.x transport connection broken: unsupported transfer encoding: "identity"

github.com

f:id:rasukarusan:20210127223005p:plain
2.2.0.4→2.5.0.0にアップデートしたら実行できるようになった

はてなブログでgifの代わりにmp4で投稿するために、Github Actionsでgif→mp4変換を自動化した

f:id:rasukarusan:20210125191908p:plain:w500

gifの欠点

  • ファイルサイズがでかい → ページ読み込みが遅くなる
  • シークバーが表示されない → 一時停止、早送りができない

MP4にしてvideoタグで埋め込むといい

<video controls muted autoplay playsinline width="95%">
  <source src="mp4動画のURL">
</video>

gifの圧縮アルゴリズムはそこまで最適化されていないため、MP4などの動画フォーマットのほうがファイルサイズを小さくできるらしい。
またvideoタグで埋め込むのでシークバーが表示されるようになる。

こんな感じで再生できるようになる

gif→mp4の変換

コマンド。ffmpegがない場合brew install ffmpegでインストールできる。

ffmpeg -i org.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" video.mp4

いちいち変換するのが面倒くさいのでGithub Actionsする

gif撮ってpushしたらmp4動画の作成が終わっている、という感じにしたい。

github.com

workflow

下記を満たすようなstepを書いた。

  • gifが追加されたとき: mp4の作成
  • gifがリネームされたとき: 以前の名前で作られたmp4を削除し、新たな名前でmp4を作成
  • gifが削除されたとき: mp4の削除
  • gif以外のファイル: 対象外にする
# .github/workflows/convert.yml
name: Convert GIF To MP4

on:
  push:
    branches:
      - master
jobs:
  build:
    name: Convert GIF
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Setup ffmpeg
        uses: FedericoCarboni/setup-ffmpeg@v1-beta
        id: setup-ffmpeg

      - name: Convert
        run: |
          git show --pretty="format:" --name-status HEAD | grep '.gif$' | while read target; do
            read status gif <<< "$(echo $target | awk '{print $1,$NF}')"
            printf "\e[31m===========${gif}============\e[m\n"
            filename=$(basename $gif | sed 's/\.[^\.]*$//')
            dir=$(dirname $gif)
            mp4=${dir}/${filename}.mp4
            case "$status" in
              'A') # add
                echo 'add'
                [ ! -e $mp4 ] && ffmpeg -i $gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" $mp4 </dev/null
                ;;
              [R]*) # rename
                echo 'rename'
                read pre_mp4 <<< "$(echo $target | awk '{print $2}' | sed 's/\.[^\.]*$/.mp4/' )"
                [ -e $pre_mp4 ] && git rm $pre_mp4
                [ ! -e $mp4 ] && ffmpeg -i $gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" $mp4 </dev/null
                ;;
              'D') # delete
                echo 'delete'
                [ -e $mp4 ] && git rm $mp4
                ;;
            esac
          done

      - name: Commit and push
        run: |
          git status
          if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
            git config --local user.email "action@github.com"
            git config --local user.name "GitHub Action"
            git add -A
            git commit -m "convert gif to mp4"
            git push origin master
          fi

f:id:rasukarusan:20210125190201p:plain

pushしたらgifがmp4に変換される

f:id:rasukarusan:20210125190144p:plain

pushしたのはgifだけなのにmp4が出来上がっている状態

f:id:rasukarusan:20210125190125p:plain

mp4動画のURLをコピーできる

終わり

Github Actionsこんな使い方もあるんだ。楽しい。

参考

Github Actionsのアクション作ってみる(Typescript編)

f:id:rasukarusan:20210122225003p:plain:w500

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。リリースノートに記載するためのもの。

f:id:rasukarusan:20210122223959p:plain

1つ前のtagからの差分コミットを一覧出力

作り方

基本的に公式のJavascriptでの独自アクションの作り方と、Typescriptで書かれたテンプレートのリポジトリを見ながらやればOK。

docs.github.com github.com

完成形

github.com

~/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')のようにしても標準出力の内容が受け取れない。返ってくるのは01などの終了コードのみ。標準出力を受け取りたい場合、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 }}

すっきり。

f:id:rasukarusan:20210122223756p:plain

tagをpushして動作確認

終わり

Docker、Typescriptの両方でアクションを作成することができた。今回みたいなちょっとしたスクリプトレベルだったらTypescriptじゃなくていい。
もうちょっとヘビーな処理とかチーム開発だったら、絶対Typescript選びたくなるんでしょうね。

Github Actionsのアクションを作ってみる(Docker編)

f:id:rasukarusan:20210122144649p:plain:w500

1つ前のtagからの差分を出すアクションを作ってみる

前回GithubActionsを使ってHomebrewのリリースを自動化した。

www.rasukarusan.com

上記で実行している「1つ前のtagからの差分を取得する」をアクションとして切り出してみる。

イメージとしてはrunでゴリゴリ書いている箇所が、

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

usesで独自アクションを指定して、シンプルな形になる予定。

steps:
  # 1つ前のtagからの差分を取得
  - name: Get commit summary
    id: get_commit_summary
    uses: ./.github/actions/get-commit-summary

アクションの作成

docs.github.com

DockerまたはJavascriptでアクションを作れるみたい。

今回はとりあえずDockerでアクションを作ってみる。

完成形

.githubに新たにactionsディレクトリを作成。独自アクションはここにいれていく。

ディレクトリ構造

~/gitblamer
$ tree
.
├── .github
│   ├── actions
│   │   └── commit-summary
│   │       ├── Dockerfile
│   │       ├── action.yml
│   │       └── entrypoint.sh
│   └── workflows
│       └── release.yml
├── README.md
└── gitblamer

独自アクションを特定のリポジトリだけで使うプライベートアクションの場合、.github/actionsに作ればいい。他のリポジトリからも使えるようなパブリックアクションの場合、パブリックなリポジトリを作ってその中に置く必要がある。
プライベートアクションからパブリックアクションにするのは一瞬で出来るため、一旦プライベートアクションとして作る。

Dockerfile

# コードを実行するコンテナイメージ
FROM alpine/git
# アクションのリポジトリからコードファイルをコンテナのファイルシステムパス `/`にコピー
COPY entrypoint.sh /entrypoint.sh
# dockerコンテナが起動する際に実行されるコードファイル (`entrypoint.sh`)
ENTRYPOINT ["/entrypoint.sh"]

今回はgitコマンドを使いたいのでコンテナイメージにalpine/gitを指定。

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:
  commit_summary:
    description: 'commit summary'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.ref }}

引数の設定などをする。

entrypoint.sh

#!/bin/sh

NEW_TAG=$1

PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n 2p)
COMMIT_SUMMARY="$(git log --oneline --pretty=tformat:"%h %s" $PREVIOUS_TAG..$NEW_TAG)"
COMMIT_SUMMARY="${COMMIT_SUMMARY//$'\n'/'%0A'}"
echo ::set-output name=COMMIT_SUMMARY::$COMMIT_SUMMARY

アクションの肝。stepとして書いていた箇所をほぼそのままコピって貼り付けるだけ。
${{ github.ref }}などstep内でしか参照できない箇所などを修正する。
chmod +x entrypoint.shで実行権限を付与しておく。

workflows/release.ymlを修正したら終了

修正後のworkflows/release.yml

       # 1つ前のtagからの差分を取得
       - name: Get commit summary
+        uses: ./.github/actions/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
+        with:
+          ref: ${{ github.ref }}

すっきりした。

tagをpushしてActionsを確認。ちゃんと前回のDockerで実行されている。

f:id:rasukarusan:20210122142255p:plain

"Get commit summary"がDockerで実行されている

f:id:rasukarusan:20210122142342p:plain

リリースノートもバッチリ作成されている

独自アクションを他のリポジトリでも使えるようにする

パブリックなリポジトリを作ってその中にactionsディレクトリの中身をいれるだけ。
リポジトリ側で特に設定する箇所もない。

mkdir commit-summary
cd commit-summary
cp -r ~/gitblamer/.github/actions/* ./
git init 
git remote add origin git@github.com:Rasukarusan/commit-summary.git
git add -A
git commit -m 'initial commit'
git tag v1
git push origin v1

github.com

"Publish this Action to Marketplace"というアラートが出るが、別にしなくても他リポジトリから使えるようになるのでしなくてOK。

f:id:rasukarusan:20210122142450p:plain

Marketplaceに出したい人だけ出せばいい

あとはusesの指定を変更したら終了。

      # 1つ前のtagからの差分を取得
      - name: Get commit summary
-       uses: ./.github/actions/commit-summary
+       uses: Rasukarusan/commit-summary@v1
        id: get_commit_summary
        with:
          ref: ${{ github.ref }}

f:id:rasukarusan:20210122142603p:plain
Rasukarusan/commit-summaryが指定されて動いてる

終わり

今回はDockerで作ってみたが、Dockerはコンテナのビルドおよび取得のレイテンシがかかるため、Javascriptより遅いらしい。 次はJavascriptでアクションを書いてみよう。

今回使ったリポジトリ

GithubActionsでリリースとFormulaリポジトリの更新を自動化した

f:id:rasukarusan:20210120214244p:plain:w500

Homebrewの自作CLIツールの配布がとても面倒くさい

以前Homebrew/tapによる配布方法をまとめたが、やることが結構あって面倒くさい。
どうやらGithubActionsを使えばめちゃくちゃ楽にできるみたいなのでやってみた。

今までの流れ

1. tagをpush
2. GithubでReleaseの作成
3. Formulaファイルの作成or更新
5. 完了

今回目指すのはこれ

1. tagをpush
5. 完了
  • Githubのページを開いてReleaseノートを作成
  • Formulaリポジトリの.rbファイルを更新

この2つの作業を撲滅する。

死ぬほど参考にさせていただいたサイト

基本的に下記のサイトを参考にすればいけました、感謝です。
参考にしていて詰まったところと、変更している箇所を書いていきます。

sasa5740.hatenablog.com

対象リポジトリ

FormulaリポジトリにはFormulaディレクトリを設置していて、複数のツールが登録してある状態。ツールごとにFormulaリポジトリを用意せず、1つのリポジトリで管理するスタイルです。

流れ

  1. リリースノートを作成するworkflow(リリース用のリポジトリ)
  2. Formulaファイルを作成/更新するworkflow(Formula用のリポジトリ)

リリースノートを作成するworkflow

リリースノートには

  • 前回のtagからの差分
  • バイナリファイルのアップロード

を載せます。

リリースリポジトリに.github/workflows/release.ymlを作っていきます

name: Release

on:
  push:
    tags:
      - 'v*'
jobs:
  build:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      # 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

      # リリースノートの作成
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          body: |
            ${{ steps.get_commit_summary.outputs.COMMIT_SUMMARY }}
          draft: false
          prerelease: false

      # バイナリファイルのアップロード
      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./gitblamer
          asset_name: gitblamer
          asset_content_type: application/octet-stream

      # Formulaに値を渡す準備
      - name: Set value for formula
        id: set_value_for_formula
        run: |
          SHA256=$(openssl dgst -sha256 gitblamer | awk '{print $2}')
          echo ::set-output name=SHA256::$SHA256
          echo ::set-output name=BINARY::gitblamer
          echo ::set-output name=CLASS::Gitblamer

      # Formulaリポジトリ更新(Formulaリポジトリのworkflowを発火)
      - name: Update Formula Repository
        uses: peter-evans/repository-dispatch@v1
        with:
          token: ${{ secrets.REPO_ACCESS_TOKEN }}
          repository: Rasukarusan/homebrew-tap
          event-type: released
          client-payload: '
            {
              "ref": "${{ github.ref }}",
              "sha256": "${{ steps.set_value_for_formula.outputs.SHA256 }}",
              "binary": "${{ steps.set_value_for_formula.outputs.BINARY }}",
              "url": "${{ steps.create_release.outputs.upload_url }}",
              "class": "${{ steps.set_value_for_formula.outputs.CLASS }}"
            }
          '

とりあえずこれでtagをpushしたらリリースノートが作られます。

# tagをpush
git tag v1.2.2
git push origin v1.2.2

f:id:rasukarusan:20210120212131p:plain

Formulaファイルを作成/更新するworkflow

本来手動でbrew createコマンドで作るファイルを、step内で作っている感じですね。

name: Update Formula
on:
  repository_dispatch:
    types: [released]
jobs:
  myEvent:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Update formula file
        run: |
          VERSION=$(echo ${{ github.event.client_payload.ref  }} | sed -e "s#refs/tags/##g")
          BINARY=${{ github.event.client_payload.binary }}
          URL="https://github.com/Rasukarusan/$BINARY/releases/download/$VERSION/$BINARY"
          data=$(cat <<EOF > Formula/$BINARY.rb
            class ${{ github.event.client_payload.class }} < Formula
              url "$URL"
              sha256 "${{ github.event.client_payload.sha256 }}"
              def install
                bin.install "$BINARY"
              end
            end
          EOF
          )
      - name: Commit version change
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add -A
          git commit -m "update version"
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

この状態でリリースリポジトリにtagがpushされたら新しいformulaファイルがコミットされる。

f:id:rasukarusan:20210120212208p:plain

f:id:rasukarusan:20210120212929p:plain
ちゃんとファイルが更新されてる

ここまで終わったらTerminalでCLIツールを更新できるようになっている。

brew info gitblamer
brew upgrade gitblamer

f:id:rasukarusan:20210120212218p:plain

詰まったところ

前回のtagからの差分を出す

# 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

こちらは下記サイトを参考にさせていただきました。

zenn.dev

注意点として、最初のstepでCheckoutするときにwith: fetch-depth: 0を設定しないと前回のtagが取得できないので注意。デフォルトでは1らしい。

バイナリファイルのアップロード

# バイナリファイルのアップロード
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
  upload_url: ${{ steps.create_release.outputs.upload_url }}
  asset_path: ./gitblamer
  asset_name: gitblamer
  asset_content_type: application/octet-stream

Homebrewではバイナリを直接指定する方式を取っていたため、zipではなくバイナリをアップロードする必要があります。
つまりasset_content_typeapplication/octet-streamにする。

Github Actionsのusesって何?

誰かが作成したアクション。自分で作ることもできる。Githubが公式でいくつか用意している。
Marketplaceで色んなworkflowが検索できる。

Formulaリポジトリのworkflowを発火する箇所も、本来はcurlでGithubAPIを叩くだけなので別にusesを使わなくてもいいが、使ったほうがcurlで書くよりシンプルに見やすくなる。

# Formulaリポジトリ更新(Formulaリポジトリのworkflowを発火)
- name: Update Formula Repository
uses: peter-evans/repository-dispatch@v1
with:
  token: ${{ secrets.REPO_ACCESS_TOKEN }}
  repository: Rasukarusan/homebrew-tap
  event-type: released
  client-payload: '
    {
      "ref": "${{ github.ref }}",
      "sha256": "${{ steps.set_value_for_formula.outputs.SHA256 }}",
      "binary": "${{ steps.set_value_for_formula.outputs.BINARY }}",
      "url": "${{ steps.create_release.outputs.upload_url }}",
      "class": "${{ steps.set_value_for_formula.outputs.CLASS }}"
    }
  '

${{ github.ref }} の他には何がある?

docs.github.com

終わり

自作ツールのアップデートが最高に楽になった、やったね。