去年 DEV のアカウントを作成したものの、今まで全く有効活用出来ていませんでした。
DEV には カノニカル URL を設定出来るので、常々 Zenn の記事を投稿する際にクロスポストしたいなと考えておりました。そこで、Zenn に記事を投稿したら、自動的に DEV にも記事を投稿 & 同期する GitHub Action を作ってみました。
sync-zenn-with-dev-action
今回初めて GitHub Action を自作したのですが、その中で得た知見を残す形で記事を書くことにしました。また、GitHub Action は TypeScript で作成しました。
まずはザッとどのような GitHub Action を作成したのか、概要について説明いたします。
GitHub リポジトリで管理している Zenn の記事を DEV に同期して投稿する GitHub Action を作成しました。 その際に DEV へ投稿する記事には Zenn の該当記事へのカノニカル URL も自動で設定できます。これにより DEV と Zenn へ記事をシームレスにクロスポストすることが可能となります。
今回作成した GitHub Action を利用するワークフローファイルの一例は下記となります。
name: "Sync all Zenn articles to DEV"
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout my project
uses: actions/checkout@v2
- name: dev.to action step
uses: nikaera/sync-zenn-with-dev-action@v1
# id を設定することで、後のジョブで Output で指定した値が参照可能になる
id: dev-to
with:
# DEV の API キーを指定する
api_key: ${{ secrets.api_key }}
# (オプション) DEV に記事を投稿した際に Zenn のカノニカル URL を設定したい場合に指定する
# username: nikaera
# (オプション) 改行区切りで指定した articles フォルダ内のファイルパスを記載した txt ファイルを指定することで、記載された記事のみを同期するようになる。
# 他プラグインと組み合わせることで差分のみを txt ファイルに載せることが可能。詳細については後述の Outputs の項目に記載。
# added_modified_filepath: ./added_modified.txt
# (オプション) Zenn の articles 以下全ての記事を常に DEV に同期するか指定する
# update_all が true のときは added_modified_filepath は無視される。
# update_all: false
# 上記アクション実行時に DEV に新規で同期する記事に関しては Zenn のマークダウンヘッダに
# 該当する DEV の記事の ID が dev_article_id として記載されるようになる。
# 今後はその ID を元に同期するようになるため、該当する Zenn の記事をコミットする。
# 新規で同期する記事が無ければ、このジョブは実行しない。
- name: write article id of DEV to articles of Zenn.
run: |
git config user.name github-actions
git config user.email [email protected]
git add ${{ steps.dev-to.outputs.newly-sync-articles }}
git commit -m "sync: Zenn with DEV [skip ci]"
git push
if: steps.dev-to.outputs.newly-sync-articles
# Outputs には DEV の記事情報 (title, url) が含まれるようになるため、
# 最後に出力して実行結果の内容を確認することもできる
- name: Get the output articles.
# dev-to という id が紐付いたジョブの Outputs を取得して echo で内容を出力する
run: echo "${{ steps.dev-to.outputs.articles }}"
簡単に nikaera/sync-zenn-with-dev-action@v1
というジョブの内部処理について説明いたします。
update_all
及び added_modified_filepath
で受け取った情報を元に、dev_article_id
の記載があるか判定するapi_key
を利用して、Zenn の記事に dev_article_id
が含まれていれば、username
を利用して、dev_article_id
を書き込む:::
記法や一部のコード記法)newly-sync-articles
に設定するdev_article_id
の含まれた記事をコミットしたいため)articles
に設定するInputs と Outputs の内容一覧については下記になります。
Inputs
キー | 説明 | 必須 |
---|---|---|
api_key | DEV の API Key を設定する | o |
username | Zenn の 自分のアカウント名 を設定する (DEV に同期する記事に Zenn のカノニカル URL を設定したい場合のみ) | x |
added_modified_filepath | 改行区切りで指定した articles フォルダ内のファイルパスを記載した txt ファイルを指定することで、記載された記事のみを同期するようになる。PR やコミット差分のファイルのみを取得するための GitHub Action jitterbit/get-changed-files@v1 と組み合わせることで、更新差分のあった記事のみを随時同期することも可能。2 更新差分のあった記事のみを随時同期するための実際のワークフローファイルはこちら | x |
update_all | Zenn の全ての記事をどうきするかどうかを設定する。GitHub Action 初回実行時のみ true にする使い方を想定している。デフォルトは true。added_modified_filepath よりも update_all が優先されるため added_modified_filepath を設定する場合は false を設定する必要あり` |
x |
Outputs
キー | 説明 |
---|---|
articles | 同期された DEV の記事のタイトル及び URL が格納された配列 |
newly-sync-articles | DEV で新たに新規作成された Zenn 記事のファイルパスが格納された配列。実際のワークフローファイルの該当する記述のように、必ずコミットに含めるようにする必要がある (理由は後述) |
Inputs 及び Outputs については公式サイトの説明をご参照ください。
Zenn の記事を新規で DEV に同期する際は、DEV に記事を新規作成する必要があります。その際に Zenn の記事と DEV の記事を紐付けるための何らかの仕組みが必要となります。そうしないと、今後 Zenn の記事内容を更新した際に、DEV のどの記事に内容を同期させればよいかが不明なためです。
そこで、記事を同期するための仕組みとして、dev_article_id
というフィールドを Zenn のマークダウンヘッダに追記することで DEV の同期すべき記事との紐付けを行うことにしました。dev_article_id
には DEV の記事作成 API 実行時の返り値である id
を設定します。
一度 id
を dev_article_id
として Zenn の記事に紐付けてしまえば、次回以降に記事の同期を行う際は DEV の記事更新 API を利用できます。
また、Outputs の newly-sync-articles
には新規で作成された DEV 記事の id
である dev_article_id
が追記された Zenn 記事のファイルパスが格納されています。そのため、nikaera/sync-zenn-with-dev-action@v1
実行後は、下記のように steps.dev-to.outputs.newly-sync-articles
内に格納されたファイル群をコミットに反映させる必要があります。
# `nikaera/sync-zenn-with-dev-action@v1` 実行後に必ず定義すべきジョブ
# DEV に新規に作成した記事がなければ実行しない (if: steps.dev-to.outputs.newly-sync-articles)
- name: write article id of DEV to articles of Zenn.
run: |
git config user.name github-actions
git config user.email [email protected]
git add ${{ steps.dev-to.outputs.newly-sync-articles }}
git commit -m "sync: Zenn with DEV [skip ci]"
git push
if: steps.dev-to.outputs.newly-sync-articles
上記のジョブで newly-sync-articles
に格納された dev_article_id
が追記された Zenn 記事は随時コミットに反映しないと、Zenn の全ての記事が同期毎 DEV に新規作成され続けるという不具合を引き起こしてしまうので、ご注意ください
サクッと開発に取り組みたかったため、Docker コンテナを利用する方法 ではなく、JavaScript を利用する方法 で開発を進めていくことにしました。
GitHub 公式が TypeScript で GitHub Action を作るための テンプレートプロジェクト を用意してくれています。今回はこのテンプレートプロジェクトを利用する形でプロジェクトを作成しました。
(余談) GitHub Action では Docker コンテナ を用いてワークフローを実行可能です。そのため、実行環境は自由に設定出来ます。(Go, Python, Ruby, etc.)
早速 TypeScript のテンプレートプロジェクトを元に自分の GitHub Action プロジェクトを作成します。
1. テンプレートプロジェクトを元に GitHub Action の TypeScript プロジェクトを作成する
2. プロジェクトの作成後 git clone
してきて開発する準備を整える
テンプレートプロジェクトを git clone
したら、まずは action.yml
の内容を変更します。
今回作成した GitHub Action の action.yml
は下記となっております。
# action.yml
# GitHub Action のプロジェクト名
name: 'Sync Zenn articles to DEV'
# GitHub Action のプロジェクト説明文
description: 'Just sync Zenn articles to DEV.'
# GitHub Action の作者
author: 'nikaera'
# GitHub Action に渡せる引数の値定義
inputs:
api_key:
# フィールドの指定が必須であれば true、必須でなければ false を設定する
# DEV の API キーは同期を行う際に必須なため、true を設定している
required: true
# フィールドの説明文
description: 'The api_key required to use the DEV API (https://docs.forem.com/api/#section/Authentication)'
username:
required: false
description: "Zenn user's account name. (Fields to be filled in if canonical url is set.)"
articles:
required: false
description: "The directory where Zenn articles are stored."
# フィールドにはデフォルト値を指定することも可能
# Zenn の記事がデフォで格納されているフォルダ名を指定している
default: articles
update_all:
require: false
description: "Whether to synchronize all articles."
default: true
added_modified_filepath:
required: false
description: |
Synchronize only the articles in the file path divided by line breaks.
You can use jitterbit/get-changed-files@v1 to get only the file paths of articles that have changed in the correct format.
(https://github.com/jitterbit/get-changed-files)
# GitHub Action 実行後に参照可能になる値定義
outputs:
articles:
description: 'A list of URLs of dev.to articles that have been created or updated'
newly-sync-articles:
description: 'File path list of newly synchronized articles.'
# GitHub Action の実行環境
runs:
using: 'node12'
# テンプレートプロジェクトでは コンパイル先が dist になるため `dist/index.js` を指定している
main: 'dist/index.js'
TypeScript のテンプレートプロジェクトでは、バンドルツールとして ncc
が採用されています。GitHub Action 実行時に使用されるのは ncc によりコンパイルされた単一の JavaScript ファイル (dist/index.js
) になります。
あとは src
フォルダ内でプログラムを書いて、npm run all && node dist/index.js
のようにコマンド実行しながら開発を進めていくだけです。
(余談) GitHub Action の開発ツールとして Docker を利用した act
というものが存在するようです。ローカル環境で検証する際は 既知の問題 に対応する必要がありそうですが、GitHub Action の開発で非常に有効活用できそうで気になっております。
今回の開発では利用しなかったのですが、今後開発を進めていく中で利用する機会も出てきそうなので、その際は本記事内容を更新する形で知見を追記したいと考えております。
:::
GitHub Action を実装する際に利用した機能を、実際のコード内容を抜粋して簡単に説明していきます。下記で紹介する内容は GitHub Actions Toolkit の機能です。
/**
下記の yml の with で指定した値は core.getInput で受け取ることが可能。
- name: dev.to action step
uses: nikaera/sync-zenn-with-dev-action@v1
id: dev-to
with:
# DEV の API キーを指定する
api_key: ${{ secrets.api_key }}
*/
core.getInput("api_key", { required: true });
core.getInput("update_all", { required: false });
/**
下記の yml の steps.<ジョブで指定した id>.outputs で参照可能な値をセットすることが可能。
セットする内容は文字列である必要がある。
- name: dev.to action step
uses: nikaera/sync-zenn-with-dev-action@v1
id: dev-to
- name: Get the output articles.
run: echo "${{ steps.dev-to.outputs.articles }}"
*/
core.setOutput("articles", JSON.stringify(devtoArticles, undefined, 2));
core.setOutput("newly-sync-articles", newlySyncedArticles.join(" "));
/**
GitHub Action 実行時に出力されるログをレベルごとに出力することが可能
core.debug はローカル実行時のみに出力内容を確認することができる
*/
core.debug("debug");
core.info(`update_all: ${updateAll}`);
core.error(JSON.stringify(error));
上記だけ把握してれば GitHub Action の開発は問題なく行うことができました。
ローカル環境で一通り開発が完了したら、GitHub リポジトリに push した後タグ付けを行います。GitHub Action はタグを設定しないと実行できないため必要な作業となります。 今回タグ付けは GitHub 上で行いました。
1. タグの項目をクリックする
2. Create a new release
ボタンをクリックしてタグ作成画面に遷移する
3. Publish release
ボタンをクリックしてタグの作成を完了する
上記の例では v1
というタグを作成したので nikaera/sync-zenn-with-dev-action@v1
のような記述で GitHub Action を利用可能になりました。 私は Zenn の記事を zenn.dev
というリポジトリで管理しているため、早速このリポジトリに GitHub Action を導入してみます。
本記事の GitHub Action では DEV の API キーを使用するため、事前にシークレットへ API_KEY
という名称で値を登録しておきます。公式サイトの手順 に従い シークレットの登録が完了したら、該当リポジトリに .github/workflows/sync-zenn-with-dev-all.yml
というワークフローファイルを作成します。
# .github/workflows/sync-zenn-with-dev-all.yml
name: "Sync-All Zenn with DEV"
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[skip ci]') == false
steps:
- name: setup node project
uses: actions/checkout@v2
- name: dev.to action step
uses: nikaera/sync-zenn-with-dev-action@v1
id: dev-to
with:
api_key: ${{ secrets.api_key }}
# Zenn の自分のアカウント名を指定すると
# DEV 記事のカノニカル URL に Zenn 記事の URL を指定できる
# username: nikaera
update_all: true
- name: write article id of DEV to articles of Zenn.
run: |
git config user.name github-actions
git config user.email [email protected]
git add ${{ steps.dev-to.outputs.newly-sync-articles }}
git commit -m "sync: Zenn with DEV [skip ci]"
git push
if: steps.dev-to.outputs.newly-sync-articles
- name: Get the output articles.
run: echo "${{ steps.dev-to.outputs.articles }}"
上記は手動実行が可能な Zenn の記事を全て DEV に同期するためのワークフローファイルになります。作成が完了したらワークフローファイルを実行して、記事が正常に同期できているか確認してみます。
1. Actions
タブをクリックする
2. 作成した Sync-All Zenn with DEV
ワークフローファイルを選択する
3. Run workflow
ボタンを実行して、ワークフローを実行する
4. ワークフローの実行が正常に完了していれば、ログに DEV の記事情報が出力される
5. dev.to
にアクセスして Zenn の記事情報が正常に同期されていることを確認する
正常に Zenn の全ての記事が DEV に同期されていることが確認できたら、次は Zenn の記事が新規作成されたり、更新されたときのみに DEV に同期するためのワークフローファイルを作成します。
main
ブランチが更新されたときのみ更新された記事のみを DEV に同期するためのワークフローファイルを作成します。該当リポジトリに .github/workflows/sync-zenn-with-dev.yml
というワークフローファイルを作成します。
# .github/workflows/sync-zenn-with-dev.yml
name: "Sync Zenn with DEV"
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[skip ci]') == false
steps:
- name: setup node project
uses: actions/checkout@v2
- name: get modified files
id: files
uses: jitterbit/get-changed-files@v1
- name: output modified files to text
run: |
for changed_file in ${{ steps.files.outputs.added_modified }}; do
echo "${changed_file}" >> added_modified.txt
done
- name: dev.to action step
uses: nikaera/sync-zenn-with-dev-action@v1
id: dev-to
with:
api_key: ${{ secrets.api_key }}
# Zenn の自分のアカウント名を指定すると
# DEV 記事のカノニカル URL に Zenn 記事の URL を指定できる
# username: nikaera
added_modified_filepath: ./added_modified.txt
update_all: false
- name: write article id of DEV to articles of Zenn.
run: |
git config user.name github-actions
git config user.email [email protected]
git add ${{ steps.dev-to.outputs.newly-sync-articles }}
git commit -m "sync: Zenn with DEV [skip ci]"
git push
if: steps.dev-to.outputs.newly-sync-articles
- name: Get the output articles.
run: echo "${{ steps.dev-to.outputs.articles }}"
上記ワークフローファイルの作成が完了したら、早速動作確認のために、まさに今執筆中の本記事内容をリポジトリに push してみます。
1. git push
後に該当するワークフローの実行結果を確認する
2. dev.to
に執筆途中の内容で記事が同期されていることを確認する
これまで説明してきた 2 つのワークフローを利用することで、大抵のユースケースはカバーできるはずです。
GitHub Action の勉強のために取り組んだプロジェクトですが、思いの外楽しくて他にも色々な機能のアイデアがあるので随時実装していきたいと考えています。(英訳, タイトルフォーマット変更, etc.)
DEV に Zenn の記事をクロスポストする GitHub Action を公開することで、いつもお世話になっている Zenn というプラットフォームを海外の方に認知していただける機会を創出できたのかもと考えたらテンションが上がってきました。
それはさておき、Zenn の記事を他でも有効活用するための GitHub Action を開発する際には、恐らく本記事で紹介した GitHub Action のコードが参考になるはずです。
また、GitHub Action の Marketplace というものが用意されているようなので、開発がある程度完了次第、こちらに申請するのも試してみたいと考えております。
ところで、Crieit さんにも記事投稿 API ができたらクロスポストできるようにしたいな...という個人的願望がありますw
DEV (Forem) の仕様上、タグは最大でも 4 つまでしか設定できないため、Zenn で設定したタグの先頭 4 つまで DEV の記事には設定しています ↩︎
当然といえば当然ですが jitterbit/get-changed-files@v1 は workflow_dispatch
でワークフローを手動実行した際や、force push
等でファイル差分を正確に特定できない操作には対応しておりませんので、その場合はエラーが発生します ↩︎
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント