GitBookでドキュメントを作りCircleCIでGitHub-pagesにpushする

今更感

github-pagesに直接何か生成して置くのもCircleCIを使うのも始めてでNode.jsを真面目に使うこともなかった人間がいきなりこれをやったらどうなるかの覚書

GitBookがクラウドサービスに注力してCLIの開発は基本的にやめるよとか言ってるタイミングで自力でやるとか何考えてるんだ?

まあGitBookでなくとも(例えばmdBookでも)基本は変わらないわけだし。mdBookはとっとと多言語サポート入れてくれ。docsifyとかは開発活発なのかな?

ところで、以下のやり方だと実質1コミットしかない短命なgh-pagesブランチが作られては上書きされて消えていく。初めはMarkdownがあるからHTMLやPDFは欲しい人が適宜作れるから気にしなくていいやと思っていたが、開発が止まって生成用のツールが入手できなくなる可能性を考えると、今後もどんどんバージョンを変えていってドキュメントも更新していくであろうプロジェクトではmaster/docsに生成したHTMLを入れてそれも一緒に更新していく方が良さそうな気がするなあ。

したいこと

GitBookを使ってMarkdownからリファレンス的なウェブサイトを生成し、それをGitHub pagesに置いて表示したいが、ローカルで毎回buildするのはたるいからCIサービスを使いたい

具体的には、以下をやる

  1. Markdownでドキュメントを書く
  2. GitBookでHTMLを生成する
  3. それをGitHub pagesに置く
  4. 上記を全てCircleCIを使って自動化してmasterにpushするたびに実行する

Markdownでドキュメントを書く

頑張って書く。

GitBookを使うなら、gitbook initするとREADME.mdSUMMARY.mdができる。README.mdは最初のページになる。SUMMARY.mdは基本的にリストがあるだけで、ここには目次を書いていく。これが最終的に左側にメニューバーとして出現し、またページをめくっていくときの順番にもなる。

だいたいこんな感じ↓

# Summary

* [Introduction](README.md)
* [Usage](Usage.md)
* [Reference](Reference.md)

GitBookでHTMLを生成する

基本的にこれはお手軽だ。ただ、少しばかり用意するべきものがある。book.jsonに使うパッケージやその設定を書く必要がある。例えば、

{
    "root": "book/",
    "plugins": [
        "hints", "anchors", "fontsettings", "search", "back-to-top-button"
    ]
}

rootはドキュメントのルートディレクトリだ。先のREADMESUMMARYを入れる場所。このjsonファイルがレポジトリのトップにあるなら、book/ディレクトリ以下に全てを置くことになる。これらの他にも使ってみているプラグインはあるが、面倒なのでスルー。気が向いたら書くかも知れない。

HTMLファイルを生成する前に、これらのプラグインをインストールする。

$ gitbook install # プラグインをインストール
$ gitbook build   # HTML生成

buildするとしばらくして_book/ディレクトリにindex.htmlその他が出力される。これをgh-pagesにぶち込めばよいわけだ。

GitHub pagesに置く

GitHub pagesでは、以下の3通りの場所にindex.htmlを置くことができる。

  • masterブランチのルート
  • masterブランチのdocs/
  • gh-pagesブランチのルート

gh-pagesdocsというのは駄目だ。

さて、_book/以下にあるものを改めてルートにしてgh-pagesに置く必要がある。ウェブページに必要のないソースコードなどはディレクトリに含めたくない。どうするか。

_book/内でgit initを実行し、_bookの内容しか持っていないレポジトリを作ってしまえばよい。そうするとソースコードは含まれなくなる。そこからおもむろにgh-pagesブランチを作り、元レポジトリのgh-pagesブランチに全てを無視してpushすればよい。

# 生成されたHTMLが入っているディレクトリに移動
     $ cd _book/
# そのディレクトリをルートとしてgit init
_book$ git init
# 仮のmasterに1コミット(虚無)
_book$ git commit --allow-empty '[ci skip] init docs'
# そこから分岐してgh-pagesを作る
_book$ git checkout -b gh-pages
# そこに_book以下全てを入れる
_book$ git add .
# _book以下全てをルートに持っている状態にしてgh-pagesにコミット
_book$ git commit -am "[ci skip] update docs"
 # USER/REPOのgh-pagesに無理やりpush
_book$ git push --force git@github.com:USER/REPO.git gh-pages

力技だが、これでなんとかなる。

だが結構アレなことをするし、いちいちこれを手元でやりたくない。そもそもgit add .とかして、ローカルにある変なファイル(秘密鍵とか中学二年生の頃に書いた日記のtxtファイルとか)を含めてしまったらどうするんだ(心配性)。全世界に公開されるんだぞ。いや絶対そんなもん自分のレポジトリ下にコピーして来ないが、CIサービスでやればそもそも毎回まっさらな仮想環境が構築されるので絶対に大丈夫だという安心感がある。

というわけで後は、これをCircleCIで自動でやる。

CircleCIからgh-pagesにpush

というわけで最低限必要なものを示す。

version: 2.1
executors:
  default:
    docker:
      - image: circleci/node:8.11.4

jobs:
  deploy:
    executor:
      name: default
    steps:
      - add_ssh_keys:
          fingerprints:
            - "<fingerprint of your ssh-key for deployment>"
      - checkout
      - run:
          name: install dependencies
          command: |
              npm install gitbook-cli
              git config --global user.name  "Your Name"
              git config --global user.email "Your Email Address"
              ./node_modules/gitbook-cli/bin/gitbook.js install
              ./node_modules/gitbook-cli/bin/gitbook.js build
              cd _book/
              git init
              git commit --allow-empty -m '[ci skip] update docs'
              git checkout -b gh-pages
              git add .
              git commit -am '[ci skip] update docs'
              git push --force git@github.com:USER/REPO.git gh-pages

workflows:
  setup_and_deploy:
    jobs:
      - deploy:
          name: update docs
          filters:
            branches:
              only: master

どこで何をしているかある程度言わないと不親切だが、私は何もわからない。俺達は雰囲気でCIサービスを使っている。

まあ、なんかCircleCI(2.1)はexecutorとして何かの言語のdockerイメージを使うらしく、これを先に設定しておくことで後々似たような設定を何度も書かなくて済むとのことらしい。npmを使いたいのでnodeを指定する。

続いて、jobsというのとworkflowsというのがあり、workflowsではjobsに登録されているものの依存関係やどういう順序で流すかなどを指定できるらしい。つまり、テストして、それが完了していたらそこで作ったバイナリをサーバーに置くとか、そういうことができる。まあ今回は使わないんですけど。

なので今回はworkflowにはdeployしか置いていない。で、deployというjobjobsの中に入れる。まず、そこで使うexecutorを指定。これはexecutorsで指定した名前(今回はdefault)で指定する。で、stepsのところに実際何をしていくかを書いていくようだ。

まず、add_ssh_keysfingerprintsを設定する必要がある。というのも、CircleCIのデフォルトのdeploy keyはRead onlyなので、gh-pagesgit push --forceなどとてもできないのだ。なのでこちらで新しくそのためだけのssh鍵を作り、登録してやる必要がある。

GitHubのレポジトリの設定から「Deploy keys」みたいなとこに飛び、「Add key」で鍵の公開鍵を書く。ここで小さなチェックボックスがあり、チェックすると書き込み権限を与えられるようになっている。厳重だ。忘れかねないので注意だ。

今度はCircleCIの「jobs」から目的のレポジトリのjobに飛び、歯車アイコンをクリックして同様にSSH鍵を登録する。CircleCIがこの鍵を使ってGitHubにpushするので、秘密鍵をコピペする必要がある。なので面倒だからと自分が普段使っている鍵を流用しようとしないように。

で、何故かこれだけだとCircleCIは足してあげたSSH鍵を使おうとしない。なので、明示的にadd_ssh_keysで足した鍵のfingerprintを指定する必要がある。忘れたら or 確認したかったらGitHubなりCircleCIで登録されてる鍵のfingerprintを見ればよい。

それをしておいたら、checkoutによってレポジトリのコードをcheckoutしてきて、それから実行するコマンドを羅列する。command:のところに書くと良い。基本的に上の感じで大丈夫だが、npmのためにpackage.jsonを書いたりgh-pagesなるパッケージをnpmで入れたりするとこの辺の操作が短くなるらしい。初心者なのでそれらが何をしているか知らずなんとなく気持ち悪いので全部手で書いた。

上手くいったらGitHub pagesにGitBookで生成した内容が表示される。確認した後、レポジトリのURL欄にでも貼ると良い。

ちなみにこれを実行するとgh-pagesブランチが突然現れた虚無のmasterからにゅっと生えたブランチということになり、GitHubのNetworkで見ると不連続になっている。ちょっとおもしろい。

ちなみに中学の頃の私は日記を書けるほどマメではなかったので中2の頃の日記のtxtファイルというものはない。