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

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

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

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

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

# やりたいこと
zennとqiitaに記事を投稿するに際して、必要なこと以外はできるだけ自動化したかったです。
要件をもう少し細かく分割すると
1. zenn cliを使ってローカルで記事が書ける（プレビューできる）
2. コマンド一発でqiitaとzennに記事が自動で投稿される

# 前提
type scriptの実行環境は作っておいてください。


# 手順
基本的には[ot07神様の記事](https://zenn.dev/ot07/articles/zenn-qiita-article-centralized)通りで動きますが、ほんの少しだけ修正しないといけないので修正します。

### 1. 変換した記事データ(frontmatter)の中にslide属性を入れてあげる
これはqiita cliの設定が変更になったっぽいですね。[参考](https://github.com/increments/qiita-cli/releases/tag/v0.5.0#:~:text=in%20%2334-,Breaking%20Changes,-Support%20slide%20mode)

{レポジトリのルート}/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

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

が完了します。