「QiitaCLI」で記事の管理が自動化！?どのように動作するかコードを解析した！


# 概要
6/30に、Qiita公式が「Qiita CLI」のベータ版を公開しました。

https://blog.qiita.com/qiita-cli-beta-release/

この機能を使用すると、Qiitaの記事の**ローカルでの作成・編集**や、**記事の自動公開**を行ってくれるようなります。

本記事は、この機能の使い方とその動作の詳細についてまとめたものになります。
# 注意
この記事は7/1時点の内容になります。機能がベータ版のため仕様が変わる可能性があります。
# 使い方
この記事では、使い方の雰囲気がわかるように一通りの流れをまとめています。
実際に使用する際には本家の「README.md」を参照することをお勧めします。**日本語で**相当丁寧に書かれており、とてもわかりやすかったです。
### Tokenの取得
認証用のアクセストークンを生成します。
以下のURLにアクセスし、「**個人用アクセストークン**」から、「read_qiita」と「write_qiita」の権限を持つトークンを生成します。

https://qiita.com/settings/applications

![スクリーンショット 2023-07-02 10.58.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/f4f59379-e7dd-ba62-c272-a9101e190db4.png)

### 環境の作成
npmを実行できる環境で、以下のコマンドを実行します。
コマンド実行後にQiitaの記事を書く環境が作成されます。
コマンドは上から、「**パッケージのインストール**」「**ディレクトリとCIの作成**」「**アクセストークンの設定**」になります。
```
npm install @qiita/qiita-cli
npx qiita init
npx qiita login
```
「**ディレクトリとCIの作成**」では、記事が配置される「publicディレクトリ」と自動リリース用の「workflow」が作成されます。
**↓実行後のディレクトリの様子**
```
> tree -I node_modules*
.
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── package-lock.json
└── package.json
```

「**アクセストークンの設定**」では、ターミナルでトークンが要求されます。
Qiitaの設定から取得した「**個人用アクセストークン**」を入力してください。
```bash
> npx qiita login
Enter your token: xxxxxxxxxxxxxxxxxxxxxxxxxxx
Hi ${your username}!
```
### 記事のpull
下のコマンドを打つと記事がローカルに同期されます。
```
npx qiita pull
```
ローカルのpublicディレクトリ配下に今までに投稿した記事が同期されます。
↓実行後のディレクトリの様子
```
> tree -I node_modules*
.
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── package-lock.json
├── package.json
└── public
    ├── 54afcdf465039ca26621.md
    └── b86afc15c883e2fda423.md
```
### リリース
以下のコマンドを実行します。
```
npx qiita publish --all
```
このコマンドで、**Qiitaの公開されている情報と比べて、変更のあった記事について**更新と反映がされます。

また、Cliでは**記事の削除は行えない**ようです。QiitaのWebページから直接削除する必要があります。
(誤操作でも記事が削除されないのは安心感があります。)

### 完成！
記事が公開されました！！

画像

# 公開される記事の対象について
記事を公開する際には以下のコマンドを実行しました。
```
npx qiita publish --all
```
引数に`--all`をつけて実行した場合、
- すでに公開されている記事で、変更があったものの反映
- 公開されていない記事のアップロードと公開

という2つが実行されるようです。
このコマンドの実装は下のようになっていました。(一部説明用に改変があります)

```ts:publish.ts
if (args["--all"]) {
  targetItems = (await fileSystemRepo.loadItems()).filter((item) => {
    // item.idは記事のUUIDのことです。未公開の記事ではnullになっています。
    return item.modified || item.id === null;
  });
} 
...
const promises = targetItems.map(async (item) => {
  let responseItem: Item;
  if (item.id) {
    responseItem = await qiitaApi.patchItem({
      ...
    });
  } else {
    responseItem = await qiitaApi.postItem({
      ...
    });
    ...
  }
});
await Promise.all(promises);
```
[ソース](https://github.com/increments/qiita-cli/blob/main/src/commands/publish.ts)

### modifiedの条件について
記事公開をするにあたり、まず現状の公開済みの記事を取得するようです。

```ts:publish.ts
await syncArticlesFromQiita({ fileSystemRepo, qiitaApi });
```

そして、取得した記事とローカルの記事を比較し、modifiedのフラグを生成していました。
```ts:file-system-repo.ts
{
  modified: !localFileContent.equals(remoteFileContent),
  ...
}
```

[ソース1](https://github.com/increments/qiita-cli/blob/5b058e6d293bf17ae41578c3fb1ec6e1f67678b9/src/commands/publish.ts#L25C25-L25C25)
[ソース2](https://github.com/increments/qiita-cli/blob/5b058e6d293bf17ae41578c3fb1ec6e1f67678b9/src/lib/file-system-repo.ts#L343C7-L343C61)

# 記事の公開に関する仕様
publicディレクトリ配下の**記事の文頭**に以下のような記述がされています。
(コメントアウトは説明のため)
```yml
---
title: newArticle001 # 記事のタイトル
tags:
  - "" # タグ（ブロックスタイルで複数タグを追加できます）
private: false # true: 限定共有記事 / false: 公開記事
updated_at: "" # 記事を投稿した際に自動的に記事の更新日時に変わります
id: null # 記事を投稿した際に自動的に記事のUUIDに変わります
organization_url_name: null # 関連付けるOrganizationのURL名
---
# title
本文.....
```
「**ymlファイル**」の形式でまとまっており、こちらを記事のメタデータとして利用しているようです。

QiitaCLIのライブラリを解析したところ、ここの処理には以下のライブラリを使用していました。

https://www.npmjs.com/package/gray-matter

### ライブラリの仕様
ファイルの文頭を参照し、その部分から下のようなオブジェクトを生成するようです。
この値を元に処理が進んでいっていました。

```yml
---
id: xxxx
title: yyyy
---
```
↑の部分から↓のオブジェクトを生成する。
```ts
{
  id: 'xxxx'
  title: 'yyyy'
}
```
</details>

# 自動リリース
自動リリースはGitHubActionsのworkflowにより実行されます。
デフォルトでは**masterあるいはmainにpushされたとき**に同期が実行されるようです。
また、workflow_dispatchによる手動実行でもできるそうです。

↓実行トリガー条件
```yml:publish.yml
on:
  push:
    branches:
      - main
      - master
  workflow_dispatch:

```
リリースには「**個人用アクセストークン**」が必要になるため、GitHubActionsの環境変数に「QIITA_TOKEN」を追加する必要があります。

### GitHubActionsの仕様
GitHubActionsでの記事の公開では、ローカルと同様で以下のコマンドが実行されていました。
```
npx qiita publish --all
```
このコマンドの処理の中で公開済みの記事を取得しており、公開や編集を反映させるかどうかについてはその差分を利用しているようです。

また、記事の公開や変更が入った際には**uuid**が取得されたり、**updated_at**が変更されたりします。この変更を同期させるために自動でcommitする処理も存在していました。

```publish.yml
...
- name: Install qiita-cli
  run: npm install -g @qiita/qiita-cli@v0.1.0
  shell: bash
- name: Publish articles
  run: qiita publish --all
  env:
    QIITA_TOKEN: ${{ inputs.qiita-token }}
  shell: bash
- name: Commit and push diff # Not executed recursively even if `push` is triggered. See https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow
  run: |
    git add public/*
    if ! git diff --staged --exit-code --quiet; then
      git config --global user.name 'github-actions[bot]'
      git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
      git commit -m 'Updated by qiita-cli'
      git push
    fi
  shell: bash
```

[ソース](https://github.com/increments/qiita-cli/blob/main/actions/publish/action.yml)
# Preview機能
`npx qiita preview`を実行すると、https://localhost:8888 でプレビュー環境を作成してくれます。
markdownファイルを元に、Qiitaで表示した時の見た目を表示してくれます。

![スクリーンショット 2023-07-01 12.19.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3493833/d35147e1-a9e9-f742-78a4-62aef1ee08a8.png)

編集はIDEから編集することを前提としているようです。プレビュー環境は**記事の表示用**で、編集をこの画面から行うわけではありません。
また、**ホットリロード**が搭載されており、markdownを編集するたびに自動で変更を反映してくれていました。
# 画像のuploadについて
QiitaCliでは画像の同期は行ってくれず、別途アップロードを行う必要があるようです。
下のURLにアクセスすると、画像をアップロードするためのページに遷移できます。

https://qiita.com/settings/uploading_images

このページに画像をアップロードするとURLを取得できるので、それを記事内で利用するようです。

![スクリーンショット 2023-07-02 10.58.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534453/732fbc9f-c05e-5743-3467-58b3f142b92a.png)
# まとめ
管理や記事の編集がローカルで行えるのは個人的にすごく嬉しく感じています。
使い慣れたエディタとプレビューの表示を別で管理しながら記事の作成ができるのはすごくありがたく、予想以上に捗ります。
また、gitを利用した管理を行えるため、commitやbranchの恩恵を受けられるのがありがたいです。

加えていうと、Organizationで記事を書いているところであれば、**複数人による記事の管理**、**issueによる進捗管理**、**社内環境でプレビューを行う**などもすることができるため、さらなる恩恵を受けられるのでないでしょうか...?