📂

指定のディレクトリ下をブラウザ上で探索可能にする GitHubActions を作った

2024/12/31に公開

概要

Generate Directory Listing Action という GitHubActions を作った。
https://github.com/yKicchan/generate-directory-listing-action

細かい使い方についてはREADMEへどうぞ(この記事では触れない)

なにができるの?

文字にするとタイトルの通り「指定のディレクトリ下をブラウザ上で探索可能にする」となる。
Repository にある Demo を見てもらえるとどういう意味かわかりやすいと思う。

Demoの画像

generate-directory-listing-action の Demo 画像

具体的には以下の通り

  1. 指定のディレクトリ以下を探索
  2. ディレクトリ下のファイル一覧を生成
  3. ファイル一覧を index.html に整形して保存

これによって、生成された index.html から指定のディレクトリ以下がブラウザ上で探索可能になるという感じ。
もちろんネストされていても掘っていって各ディレクトリごとに index.html を生成する。

なんで作ったの?

端的に言えば、GitHubPages に、デフォルトでファイル一覧を表示する機能がないから作った。

ファイル一覧がブラウザで表示されている機能の画像

ファイル一覧がブラウザで表示されている機能の画像

例えば Document の Markdown とか CoverageReport とか Storybook とかいろんなシチュエーションで複数のページを GitHubPages にデプロイするとして、いざルートにアクセスすると 404 が表示されてしまう。
(自分は Marp のスライドをホスティングしており、各スライドページに簡単に辿れるようにしたかった)

解決するには目的のページの URL を直打ちするか、目的のページまでの Index を自作してルートに設定するかが多いと思う。
とはいえ直打ちは面倒だし、Index を手書きするのは整合性が取れない or 自動化が手間だったりする。

これを解決する Action 作成の先駆者を一人だけ見つけており、自分は元々この Action を利用していた。
https://github.com/jayanta525/github-pages-directory-listing

しかしこれは指定のディレクトリ以下の、"全てのファイル"を列挙する動作しかサポートされていない。
例えば特定のファイルを除外したりという拡張性がない点が気になっていた。
「じゃあ OSS なんだから PR 出してこいよ」と言われそうだが、Python で HTML を文字列結合で頑張っていたので正直しんどいしメンテナンス性もよくない。

なのでイチから作った。ぱわー。(体言止め)

なにがうれしいの?

前述の通り、目的のページの URL を入手する手間が解消されたり、ルートに自作した Index ページのアプデの手間やそれを自動化する必要がなくなったりする。
それくらいのちょっとだけのうれしさ。

技術的な話

ここから本題というか、技術的な知見を書き連ねていく。
とは言え GitHubActions の自作も、今回作った機構(後述するが、簡易SSGみたいなこと)は初めてやった。
なので、もっといいやり方とかはあると思うがあらかじめご了承いただきたい。
先に言っておくが、公式の TypeScript のテンプレートは初めてというのもあり素振りしたかったので使わなかった。(参考にはした)
あとはやさしくマサカリなげてね。

要件

先に要件を整理しておく。
なお成果物(出力するHTML)が簡単なものなので、できるだけ簡単な構成にし(複雑な設定などを避け)たかった。

  • GitHubActions で動作すること
  • Action の実行時にファイル一覧を探索できる index.html を生成すること
  • できるだけメンテナンスが難しくないこと
    • ドマイナーな選定はしない
    • 特に HTML 構造の管理方法に気を使う
  • 拡張性があること
    • オプションの使いやすさ
    • 機能追加のしやすさ

以上を念頭に置いて技術選定を行った。

技術スタック

項目 利用技術 選定理由
言語 TypeScript 宗教上の理由
あとランタイムが Node.js だと Actions でなにかと都合がよい
JSX Preact HTML テンプレートエンジンとして採用。
React ほどの機能は不要だったので Preact にした
ビルド esbuild 早い・軽い・簡単
ncc は JSX 動かないのでやめた
rollup は使おうとして設定めんどくさいなってなってやめた。
CSS PostCSS + cssnano minify したかった。
あとついでに Plugin 入れてネストかけるのもありがたい
Lint/Format Biome 単純に使ってみたかった
あと eslint + prettier 設定めんどい
Test vitest + happy-dom 早くて jest 互換あるのがいい
ファイル検索 glob ファイル一覧の抽出や利用者のオプション指定時には glob パターンがよかった
まだ experimental かつ機能が足りないが、近い将来は Node.js 標準パッケージで足りそう
スペシャル
アドバイザー
ChatGPT-4-turbo (v2) 雑に要件投げて機能実装の雛形とか
技術選定・実装方法の相談とか
ちょっとしたシェル書いてもらったりとか
スーパー
アシスタント
GitHubCopilot もうお前なしじゃ生きていけないよ...

今回一番悩んだのが、HTML の管理方法だった。
初めはビルド周りも考えなくていいので、古典的な文字列結合で(ただしインデントやエディタ上での syntax highlight が機能するよう配慮して)実装していた。
ただやっぱりどうしても気になって(徐々にアレルギー反応が出てきて)やめた。

最終的には JSX が使えない ncc をやめて esbuild に移行し、Preact で JSX から index.html を Action 実行時に生成するようにして、実質 SSG を自作したことになった(とは言え蓋を開けてみれば renderToString 関数呼ぶだけだったが)

あとはいい世の中になったもので、雛形の実装は ChatGPT くん(無料)に書いてもらって時短したり、ビルド周りはどうすれば良いかなどの相談もしたり、いろいろ答えてもらって意外と参考になった(失礼)
GitHubActions 自作も SSG っぽいことやるのも初めてで、知識が全然ない状態でスタートだったため彼?彼女?がいなければ実は出来上がっていないかも。


以下技術知見紹介コーナー

Preact で HTML を実行時に生成する

コード的には下記ファイルの部分。

https://github.com/yKicchan/generate-directory-listing-action/blob/main/src/core/html/index.tsx

Preact の SSR 用の renderToString という関数を使って実装した。
この関数に JSX を食わせることで、文字通り HTMLstring 結果を得ることができる。
この結果を fs.writeFile を使って index.html を生成している。

面白ポイントとしては、async なコンポーネントを作れてしまうところ。
普段の React とかではありえないが、自作機構なので async なコンポーネントを await して render するということができてしまって楽しかった。(小並感)

CSS や JS の bundle が必要な場合はまた別の build 周りの設定が必要だったり bundle ファイルを HTML に差し込む必要があったりするが、基本的にはこの考え方で SSG を自作することができる。
たまにこんな感じでブログの SSG 自作しましたって人を見かける。すごいなぁってなる。(小並感)

CSS を minify する

色々なライブラリがありさまざまなアプローチがあるが、今回は PostCSS + cssnano を利用して CSS の minify を行った。
コード的には下記ファイルの部分。

https://github.com/yKicchan/generate-directory-listing-action/blob/main/src/core/css/index.tsx

css を esbuild の機能で string として読み込み、それを PostCSS と plugin(cssnano など)に食わせて処理している。

改良余地として、グローバルにしか読んでいない(コンポーネントごとにCSSを読んでいない)ところや未使用クラスの削除には対応できていない

ここに書くには余白が足りないが、端的にいうと複数の要因から例えば cssModules どう対応するんだ?となっている。
まあ力及ばずだった。(シンプルな View を提供しているのでグローバルCSSで事足りておりわざわざ時間を使わなかったという戦略的撤退でもあるが)

ログ出力の設計

ほぼ自分が欲しくて作ったとは言え、誰かが使った時に何が起こっているかを把握できるようにおしゃれにログ出力を行った。
なおログ出力は @actions/core の機能で利用できる。
(JS 畑ならよく使う Node.js ConsoleAPI と似た IF で core.info とかするだけ。ローカル実行時には標準出力扱いとして出てくれる)
Action を自作するならみんなもぜひこだわってほしい。地味に大変だったけど。

例えばこのファイルを見てもらうと、随所でログを出力しているのがわかると思う。

https://github.com/yKicchan/generate-directory-listing-action/blob/main/src/core/generator.ts

この Action のおおまかな処理は下記の通り

  1. 利用者が設定した Input の読み込み
  2. 指定されたディレクトリ以下の探索
  3. ヒットしたディレクトリごとに HTML を生成・保存

なので、利用者が設定した Input に対して、最終的な Output(HTML の生成・保存)がログに出れば良いと思い、下記の通りログの設定をした。

  • Action の開始・終了のお知らせ
  • 生成に成功した index.html の場所とファイルサイズ
実際のログの画像

Generate Directory Listing Action 実行時のログの画像

逆に以下のような情報はあったら嬉しいが、常になくてもいいと思い、デバッグログとして出力した。
なおご存知だとは思うが、Action のデバッグログは利用者が設定しない限り表示されない。

  • 探索対象のディレクトリやファイルがどれだけヒットしたか
  • 準正常系の処理(想定されたエラー、ディレクトリの中のファイル数が0とか、すでに index.html が存在しているとか)
  • とある処理の開始地点の計測
デバッグログを有効にした場合の実際のログ画像

デバッグログを有効にした Generate Directory Listing Action 実行時のログの画像

ローカルでの実行時ログ画像

Generate Directory Listing Action のローカル実行時のログの画像

ちなみにログ画像を見ての通り、すべてのログにはいい感じに色をつけて出力するようにした
(成功系は緑とか、メッセージと値が出る時は値側に色つけるとか)

ChatGPT に雑レビューしてもらう

実装方法の相談やエラーの解決、英訳などをメインで使っていたが、それ以外にも ChatGPT くんがコードレビューツールとして地味に役立ってた。

下記は実際に AI に提案されて(ログの文字が長い場合は...で丸め込むといいよ)って言われて、確かにそれいいなとなってコミットした履歴の一つ。
https://github.com/yKicchan/generate-directory-listing-action/commit/95639bc0d83d2f1a6cfe1b8873c941643125eb51

おひとり様開発なのでコードレビューを本来はしてもらえない環境だったが、ふと思い立って ChatGPT くんに実装をコピって投げて雑に「どう?」って聞いたら割と良かった。

AIにコードレビューしてもらっている画像

シェルスクリプトの内容をAIにレビューしてもらっている画像

(履歴から拾えたのが実装ではなくリリース用のスクリプトのレビューで申し訳ない...イメージとしては伝わるかと...)

ざっくりまとめると、だいたい以下の感じでレビューしてくれた。

  • 内容をどう解釈したか
  • いいところはどこか
  • 改良点は何か
  • その改良方法の詳細

改良点がなさそうなときはいいですね!で終わるし、改良点があってもその改善方法で嘘ついたり、ドメイン知識がないので微妙な解決案だったり、もちろん完璧ではない。
いくつかは結局 Google 先生に解決してもらったり自分のパワーでなんとかしたりはした。

なので、AIの提案を取捨選択するための判断力・基礎知識がない人にはオススメしない
あくまでもサポートとしての位置付けで、ゴールまで時短できたり詰まった時のヒントがもらえたりするのが自分にとっては強いメリットだった。

いうまでもないが、無断で仕事で使うのは情シス案件ね。

テストカバレッジ 100%

ぶっちゃけテスト書こうねとしか言うことないが、せっかくなので記しておく

vitest をベースに、適度にモックしながらテストした。
テスティングトロフィーというよりはピラミッドになっているはず。
結合テストでカバーしても良かったが、一つのテストファイルが肥大化するのも嫌なのでユニットテストをガンガン書いて、結合部分はモックする感じにした。(テスト設計の正解かは知らない)

実際のテストコードは下記の感じで AAA パターンを意識したので結構読みやすいと思う(当社比)

https://github.com/yKicchan/generate-directory-listing-action/blob/main/src/core/generator.test.ts

JSX のテストは preact を利用しているので、@testing-library/preact を利用してアクセシブルかまでチェック。
とはいえ静的な DOM なのでテストも簡単な部類だが、テストの書きやすさは責務が綺麗になっているかと言う疑似的な確認もできるので良かった。

JSX の実際のテストコードは下記の感じ

https://github.com/yKicchan/generate-directory-listing-action/blob/main/src/app/table/index.test.tsx

以上の通り全体的にテストコードを整備したが、改良余地として、ファイルを探索して index.html を生成する性質上、実際のディレクトリやファイルを用意してテスト実行しているのをなんとかしたい。

テスト環境の用意は下記のとおり簡単なスクリプトを作っていて、それをテスト実行時に自動実行されるようにしているので手間はなくしているが...
https://github.com/yKicchan/generate-directory-listing-action/blob/main/scripts/sandbox

とはいえこのスクリプトが変わるとテストが簡単に落ちてしまうので、あまり気分が良くない。
モックしてもいいが、Action の根幹部分なので実際の動作で見たい気持ちがあるのでむずかしい。

大きな声で言いたいのは、ないよりある方がマシかつテストを書いてることが偉いということ。
なので、テストあんまりわからない人も臆せずユニットテストを書くことから始めよう。

まとめ

Generate Directory Listing Action という GitHubAction を作った。
GitHubPages とかでファイル探索が欲しい人はぜひ使ってみてほしい。

SSG 自作風味は思ったより簡単だったけど、それに至るまでは知らないことだらけでいろいろ大変だった。
そして最近の AI は結構すごいと思った。ちょっと前まではおもちゃだったのに...

ここまで読んでいただきありがとうございました(そんな物好きがいるとは思っていないが)
そして2024年お疲れ様でした!!
2025年も HappyHacking🤟 良いお年を〜👋

Discussion