GitHubActionsの自動PRでVSCodeの拡張機能をMarkdown管理する

## 概要

本記事は、[Visual Studio Code Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vscode) 15日目の記事です。

今回は、GitHubActionsを用いて、VSCodeの拡張機能を管理する方法を紹介します。
Markdownだと見やすそう、自動PR作成だと楽できそうという発想レベルで作った、いわゆるオレオレ実装なので、あくまで一例程度でご覧ください。

## つくったもの

https://github.com/faronan/VSCodeExtensionsManager

### Markdownファイル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/b67816fd-665c-5bb9-8992-e00c4f83c818.png)

### GitHubActionsで自動作成されるPR

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/eb17126c-c90f-b38b-a66b-99429127ff06.png)


## 機能・制作メモ

### 一括インストール/アンインストール
拡張機能のインストールはサイドバーからの操作が一般的かと思います。
ただ、実は`code`コマンドを使用することで、ターミナルからも拡張機能のインストールやアンインストール、インストール済み拡張機能の一覧表示などの操作が可能になります。

https://code.visualstudio.com/docs/editor/command-line


この`code`コマンドを使用した一括インストールの手順は以下になります。

1. `jq`コマンドを使用して、JSONデータから拡張機能の一覧を抽出
2. forループで配列の中から一つずつ拡張機能の名前を取得
3. `code`コマンドに拡張機能の名前を渡してインストール 

(一部分を抜粋)

```sh
for i in $(seq 0 $(expr ${json_length} - 1)); do
  extension=$(echo $json_content | jq ".[${i}]" | sed 's/"//g')
  code --install-extension $extension
done
```

※ 本記事に記載しているコードは、基本一部分だけ抜粋したものですので、これだけでは動きません。動くコードが必要な方はGitHubのコードをご確認くださいmm

### JSON⇨Markdownの変換

JSONをMarkdownに変換する手段を調べたところ、`pytablewriter` というPythonの便利なライブラリを見つけました。このライブラリを用いるために、今回はPythonを使用しています。
(当初はシェル芸で全てを完結させることを想定していたのですが、私では力不足でした...)

https://pypi.org/project/pytablewriter/

使い方は非常に簡単で、引数`value_matrix`に二次元配列を渡してあげるだけです。

```convert.py
from pytablewriter import MarkdownTableWriter
(中略)

writer = MarkdownTableWriter(
    headers=["name", "desc", "installs", "image"],
    value_matrix=content, # 補足: contentの中身は二次元配列です
)

with open(md_file, "a") as m_a:
    writer.stream = m_a
    m_a.write("## {}\n\n".format(key))
    writer.write_table()
```

### Gistにある最新情報の取得
手元のVSCodeに新しい拡張機能を追加したことを検知する仕組みには、拡張機能 [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync)を使います。この拡張機能のオプション`Sync: Auto Update`をオンにすると、拡張機能の追加など手元の設定が変更されたタイミングで、変更後の設定をアップロードしてくれます。

※ VSCode標準の[Settings Sync](https://code.visualstudio.com/docs/editor/settings-sync)ではありません。拡張機能の方の`Settings Sync`です。軽く動作確認をした限り、両方を同時に使っても動きそうではあります。ただ、コマンドパレットから選択するとき、どちらがどちらか分からなくなってしまいましたので、できれば避けておいたほうが良さそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/f419b736-46c5-097f-084f-879c5e2d0233.png)


ドキュメントを見たところ、将来的にVSCode標準の方のSettings SyncもAPIを公開してくれるそうなので、そうなったら乗り換えようと思います。

>Can I use a different backend or service for Settings Sync?#
Settings Sync uses a dedicated service to store settings and coordinate updates. A service provider API may be exposed in the future to allow for custom Settings Sync backends.



Settings Syncの導入手順に関しては、以下の記事が分かりやすかったです。

https://qiita.com/kazurego7/items/3dc5a381811e4d429533

全ての設定を終えれば、Gistが作成されているはずです。その中の`extensions.json`が拡張機能に関する設定ファイルです。
このファイルの右端「Raw」ボタンを選択し、JSONが表示されているURLにアクセスすれば、Gist上のJSONデータを取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/c8132e90-e9a1-73b6-ac56-8abb9e8cb870.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/b4e9bcf8-0bda-c7e9-9c44-cb3889919eb7.png)

なお、本来であれば、都度最新データのURLは変わってしまいます。このURLにはリビジョンのハッシュ値が含まれていて、Gistは更新されるたびにリビジョンのハッシュ値が変わってしまうからです。
しかし、URLからリビジョンのハッシュ値を取り除くことによって、常に最新データの取得が可能になります。

`https://gist.githubusercontent.com/[ユーザー名]/[Gist ID]/raw/[ハッシュ値]/extensions.json` 
⇨ 
`https://gist.githubusercontent.com/[ユーザー名]/[Gist ID]/raw/extensions.json` (常に最新データを取得する固定のURLをゲット✨)


### PRの自動作成

GitHubActionsでは、`schedule`トリガーイベントによって、処理の定期実行が可能です。これを利用して、Gistに記載されている拡張機能の一覧と、現在JSONで管理している`extensions.json`に、差分が生じていないかの検知を走らせます。
差分が生じていた場合、[peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request)というGitHubActionsを使って、PRを作成します。


GistのURLはコード内に直接書いても良かったのですが、念の為環境変数に埋め込んでいます。環境変数の登録は、GitHubのページ Settings > Secrets から設定できます。

``` .github/workflows/ci.yml
name: Create PullRequest extensions update

on:
  schedule:
    - cron: "0 15 * * *"

jobs:
  create_pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python 3.10.0
        uses: actions/setup-python@v2
        with:
          python-version: 3.10.0
          architecture: x64
      - name: Set up packages
        run: |
          pip install poetry
          poetry config virtualenvs.create false
          poetry install --no-interaction --no-ansi
      - name: Run
        run: make run
        env:
          GIST_URL: ${{ secrets.GIST_URL }}
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: update extensions
          author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
          signoff: false
          branch: auto_update
          branch-suffix: short-commit-hash
          delete-branch: true
          title: "[Auto update] Update extensions"
          body: |
            Update report
            - Updated extensions
              - extensions.json
              - extensions.md
            - ref. [gist setting](${{ secrets.GIST_URL }})
```

↑にも貼りましたが、作成されるPRはこんな感じです。
(リンクは[こちら](https://github.com/faronan/VSCodeExtensionsManager/pull/34))

ちなみに、PRを放置するとbotが同じ変更をforce-pushし続けます。見栄えが悪くなる前に早めにマージしてあげましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/48557be4-cecb-2d06-b739-e5197bf97ec4.png)

## 最後に
今回、ある程度使用頻度の低い拡張機能を整理したつもりですが、それでもまだ40個もの拡張機能が残っていて、びっくりしました。(整理前は68個)
拡張機能の管理テクについては、今後、他の方の仕組みも学んでいきたいところです。

最後になりますが、アドベントカレンダー初参加なので、何か拙いところなどあればご指摘ください。
ここまで読んでくださりありがとうございました。

## 参考記事

- https://note.com/teitei_tk/n/n7204cb8d97c5
- https://tarosky.co.jp/tarog/1466