Zenn の記事を private/public repository で同期する GitHub Actions
Zenn における GitHub 連携
Zenn は GitHub 連携して記事管理できる便利な機能があります。
「GitHubリポジトリでZennのコンテンツを管理する」
ただし、同期できるリポジトリは1つのみのため
- 「有料な本は Private リポジトリにしたい」
- 「無料の記事は Public リポジトリにしたい(PR 受け付けたい)」
そんな場合にどっちしか取ることができません。
そこで Private リポジトリの無料公開記事を Public リポジトリ同期する GitHub Action を書いたので紹介します。
構成
Zenn と同期するのは Private リポジトリです。
Private リポジトリで執筆します。
Private リポジトリの同期ブランチが更新(push)されると Priate リポジトリで同期アクションが実行されます。
(PR のことも考えて逆方向も対応してます)
以下で詳しく説明します。
無料記事を Public リポジトリに同期する
実際のアクションを見たほうが早いと思うので、
YAML にコメント補足するスタイルで説明します。
実際の設定はこちらから確認できます。
GitHub Action
name: 'Run sync'
on:
# 動作確認用に使ってたトリガー
# pull_request:
# Private リポジトリ に更新があった場合にトリガーされる
repository_dispatch:
types: [sync]
jobs:
sync-from-private:
runs-on: ubuntu-latest
env:
PYTHON_VERSION: 3.8
# Private リポジトリのユーザーとリポジトリ名
FROM_REPO_USER: "srz-zumix"
FROM_REPO_NAME: "Zenn"
steps:
# 自分をチェックアウト
- uses: actions/checkout@v1
# Private リポジトリをチェックアウト
- uses: actions/checkout@v1
with:
repository: ${{ env.FROM_REPO_USER }}/${{ env.FROM_REPO_NAME }}
# アクセストークンは `repo` を含んだものを Secret にセットする
token: ${{ secrets.GITHUBPAT }}
ref: master # 同期するブランチ
path: ./From
# PR 用のブランチ名作成と Private リポジトリのハッシュを取得する
- name: create branch name
id: create_branch_name
run: |
cd ../From
HASH=$(git rev-parse HEAD)
echo "##[set-output name=hash;]$(echo ${HASH})"
# echo "##[set-output name=branch;]$(echo sync/${HASH})"
# トリガーされる度に PR 作られるよりも、PR は1つで更新される方がいいと思ったので名前固定
echo "##[set-output name=branch;]$(echo sync/latest)"
# 同期用スクリプトの実行環境セットアップ
- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set Python environment variable
run: echo "LD_LIBRARY_PATH=${{ env.pythonLocation }}/lib" >> $GITHUB_ENV
# 同期
- name: sync
# Private -> Public への同期は Private 側の状態を優先するので先に全部消す
# check-publish.py が公開設定のファイルをリストアップするので、それをコピーする
run: |
rm -rf ./articles/*.md
python check-publish.py ../From/articles | xargs -I {} cp -f {} ./articles/
# PR アクション使って PR 作成
# これ便利!!
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
base: master
branch: ${{ steps.create_branch_name.outputs.branch }}
title: "Sync from private ${{ steps.create_branch_name.outputs.hash }}"
body: "sync by https://github.com/${{ env.FROM_REPO_USER }}/${{ env.FROM_REPO_NAME }}/commit/${{ steps.create_branch_name.outputs.hash }}"
commit-message: "sync by ${{ steps.create_branch_name.outputs.hash }}"
delete-branch: true
labels: "sync"
reviewers: ${{ env.GITHUB_ACTOR }}
.py
check-publishcheck-publish.py
は published: true
な Markdown を検出するスクリプトです。
check-publish.py
#!/usr/bin/env python
import os
import sys
import re
def check(path):
if os.path.splitext(path)[1] != '.md':
return False
with open(path) as f:
head = f.readline()
if head.strip() != '---':
return False
while True:
text = f.readline().strip()
if text == '---':
break
if re.match(r'^published:\strue$', text):
return True
return False
def check_and_print(path):
if check(path):
print(path)
def check_dir(dir):
for f in os.listdir(dir):
if f.startswith('.'):
continue
path = os.path.join(dir, f)
if os.path.isdir(path):
if f in ['articles', 'books']:
check_dir(path)
elif os.path.isfile(path):
check_and_print(path)
def main():
for path in sys.argv[1:]:
if os.path.isdir(path):
check_dir(path)
elif os.path.isfile(path):
check_and_print(path)
if __name__ == '__main__':
main()
Private リポジトリの push イベントで Public リポジトリのアクションをトリガーする
こちらのアクションは Private リポジトリの方に設定します。
「Github Actions で他のリポジトリからの変更通知を受け取ってPRを作成する Workflow」を参考に作成しました。
(※ GITHUB_
で始まる Secret は予約名として使えなくなったっぽいので気をつけてください)
(Public リポジトリの方にも同様の dispatch.yml があるので参考にしてください。)
name: 'Dispatch sync'
on:
push:
branches:
- master
# articles または books ディレクトリ以下で更新があった場合のみ
paths:
- 'articles/**'
- 'books/**'
jobs:
sync-trigger:
runs-on: ubuntu-latest
steps:
# Public リポジトリにイベントを送る
# (peter-evans さんお世話になっております)
- name: dispatch sync
uses: peter-evans/repository-dispatch@v1
with:
repository: srz-zumix/Zenn-public
token: ${{ secrets.GITHUBPAT }}
event-type: sync
Public リポジトリの更新を Private リポジトリに同期する
今度は逆に Public -> Private への同期を設定します。
Public リポジトリで PR などに対応した場合を想定しています。
check-publish.py
は全く同じものを使用します。
dispatch.yml
と sync.yml
もほぼ同じものを使います。
Private リポジトリなので実物を見ることができませんが、差分だけ説明します。
Private -> Public の場合は、Public 側のファイルをすべて削除して Private を優先していました。
Public -> Private の場合も、Private を優先したいので今度はファイル削除をしません。
(Private 側には未公開のファイルがあるため)
- name: sync
run: |
- rm -rf ./articles/*.md
+ # rm -rf ./articles/*.md
python check-publish.py ../From/articles | xargs -I {} cp -f {} ./articles/
あとは、リポジトリの向き先が Public <-> Private で入れ替わるだけです。
FROM_REPO_USER: "srz-zumix"
- FROM_REPO_NAME: "Zenn"
+ FROM_REPO_NAME: "Zenn-public"
- name: dispatch sync
uses: peter-evans/repository-dispatch@v1
with:
- repository: srz-zumix/Zenn-public
+ repository: srz-zumix/Zenn
token: ${{ secrets.GITHUBPAT }}
結果
今後
現時点で対応してるのは articles のみなので本を書いたら books にも対応したいと思います。
Discussion