はじめに
zennとqiita両方に同じ記事を投稿したいけれど、めんどくさいと感じたので
ローカルで記事を書いて、それを一括でzennとqiitaにアップロードするためのツールを作りました。
とは言ってもot07さんという天才的な先駆者がいらっしゃったので、その方のコードを微調整して、自動化のためのshellscriptを付けただけです。
また、公開した後に微調整するようなパターンには今は対応していません。
必要になったら順次アップグレードしていきます。
やりたいこと
zennとqiitaに記事を投稿するに際して、必要なこと以外はできるだけ自動化したかったです。
要件をもう少し細かく分割すると
- zenn cliを使ってローカルで記事が書ける(プレビューできる)
- コマンド一発でqiitaとzennに記事が自動で投稿される
前提
type scriptの実行環境は作っておいてください。
手順
基本的にはot07神様の記事通りで動きますが、ほんの少しだけ修正しないといけないので修正します。
1. 変換した記事データ(frontmatter)の中にslide属性を入れてあげる
これはqiita cliの設定が変更になったっぽいですね。参考
{レポジトリのルート}/scripts/lib/convert-frontmatter.tsの34行目あたりにに以下のコードを挿入します
dataCloned.slide = false
2. 変換した記事データの(frontmatter)の中のupdatedAtをnullから変える
nullだとプレビューが見れないので、空文字か何かしらの文字列に変えてあげないといけないです。
せっかくなので現在時刻を取得して入れてあげるようにしましょう
{レポジトリのルート}/scripts/lib/convert-frontmatter.tsを以下のように修正します。
25行目
dataCloned.updated_at = existingData.updated_at || new Date().toISOString()
30行目
dataCloned.updated_at = new Date().toISOString()
上記の二つの変更によって現在のfrontmatter.tsはこんな感じになっているはず
import { existsSync, readFileSync } from 'fs'
import matter from 'gray-matter'
import yaml from 'js-yaml'
export function convertFrontmatter(outputPath?: string) {
return function _convertFrontmatter(inputContent: string) {
const { data, content } = matter(inputContent)
const dataCloned = { ...data }
// Remove unnecessary fields
delete dataCloned.emoji
delete dataCloned.type
// Convert published to private (reversed)
dataCloned.private = !dataCloned.published
delete dataCloned.published
// Convert topics to tags
dataCloned.tags = dataCloned.topics
delete dataCloned.topics
// Add new fields
if (outputPath && existsSync(outputPath)) {
const existingData = matter(readFileSync(outputPath, 'utf8')).data
dataCloned.updated_at = existingData.updated_at || new Date().toISOString()
dataCloned.id = existingData.id || null
dataCloned.organization_url_name =
existingData.organization_url_name || null
} else {
dataCloned.updated_at = new Date().toISOString()
dataCloned.id = null
dataCloned.organization_url_name = null
}
// Add slide Option
dataCloned.slide = false
const frontmatter = yaml.dump(dataCloned)
return `---\n${frontmatter}---\n${content}`
}
}
3. qiita cliのgithub actionsを動くようにする
qiita cliの設定の時にgithub workflowの設定をしていると思います(https://github.com/increments/qiita-cli/#github-%E3%81%A7%E8%A8%98%E4%BA%8B%E3%82%92%E7%AE%A1%E7%90%86%E3%81%99%E3%82%8B)
しかしこのままでは動きません。
理由は自動生成された.github/workflows/publish.yamlがルートディレクトリにないから、github側に認識してもらえていないことです。
なので.githubディレクトリごとrootに移しちゃってください。
4. zennからqiitaへの変更と、記事の投稿を同時に行うシェルスクリプトを作る
細かいコードの解説はしません。
コメントアウトを細かく書いたので気になったら読んでください。
ルートディレクトリに以下を置いてください。
ファイル名は何でも良いですが、自分はsyncAndUpload.shにしました。
#!/bin/bash
# 1. .md形式の変更があったファイルのパスとファイル名を取得
git add .
git commit -m "zennの記事をcommit"
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD | grep '\.md$')
# 2. & 3. 変更があった.mdファイルごとに指定されたコマンドを実行
for FILE in $CHANGED_FILES; do
# 変更があったファイルのパスを表示
echo "Changed file path: $FILE"
# ファイルのパスがarticles配下ではない場合、処理をスキップ
if [[ ! $FILE =~ ^articles/ ]]; then
continue
fi
# .md拡張子を削除してファイル名を取得
FILENAME=$(basename $FILE .md)
# ファイル名と同じファイルがqiita/public配下に存在しない場合だけ、npx qiita newを実行
if [ ! -e "qiita/public/$FILENAME.md" ]; then
cd ./qiita
npx qiita new "$FILENAME"
cd ../
fi
# ./node_modules/.bin/ts-node scripts/ztoq.ts "取得したファイルパス" qiita/public/"取得したファイル名.md"を実行
./node_modules/.bin/ts-node scripts/ztoq.ts "$FILE" "qiita/public/$FILENAME.md"
done
# 最後にgit hubにあげる
git add .
git commit -m "qiitaの記事をcommit"
git push
以上です。
あとは、zennで記事を書いて
sh syncAndUpload.sh
を実行すれば
- zennの記事をqiita用に変換
- zennの記事のアップロード
- qiitaの記事のアップロード
が完了します。