🌟

zennに記事を投稿したらqiitaにも同時に投稿されるツールを作った話

2023/11/06に公開

はじめに

zennとqiita両方に同じ記事を投稿したいけれど、めんどくさいと感じたので

ローカルで記事を書いて、それを一括でzennとqiitaにアップロードするためのツールを作りました。

とは言ってもot07さんという天才的な先駆者がいらっしゃったので、その方のコードを微調整して、自動化のためのshellscriptを付けただけです。

また、公開した後に微調整するようなパターンには今は対応していません。
必要になったら順次アップグレードしていきます。

やりたいこと

zennとqiitaに記事を投稿するに際して、必要なこと以外はできるだけ自動化したかったです。
要件をもう少し細かく分割すると

  1. zenn cliを使ってローカルで記事が書ける(プレビューできる)
  2. コマンド一発で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-で記事を管理する)
しかしこのままでは動きません。

理由は自動生成された.github/workflows/publish.yamlがルートディレクトリにないから、github側に認識してもらえていないことです。

なので.githubディレクトリごとrootに移しちゃってください。

4. zennからqiitaへの変更と、記事の投稿を同時に行うシェルスクリプトを作る

細かいコードの解説はしません。
コメントアウトを細かく書いたので気になったら読んでください。

ルートディレクトリに以下を置いてください。
ファイル名は何でも良いですが、自分はsyncAndUpload.shにしました。

#!/bin/bash

# 1. .md形式の変更があったファイルのパスとファイル名を取得
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 "保存"
git push

以上です。
あとは、zennで記事を書いて

sh syncAndUpload.sh

を実行すれば

  1. zennの記事をqiita用に変換
  2. zennの記事のアップロード
  3. qiitaの記事のアップロード

が完了します。

Discussion