🦕

Denoでもgit hooksでlint-stagedする!deno.jsonc時代の開発テンプレート

2023/06/12に公開

tl;dr

拙作deno-dev-templateを更新しました。
テンプレートをそのまま使っても良いですが、deno.jsoncをローカルのdenoプロジェクトにコピーするだけでも便利タスクが使えます。
deno task setup-hooksを実行すると、git hooksが定義され、lint-stagedとcommitlint(npx使用)が動くようになります。

https://github.com/kawarimidoll/deno-dev-template

ミニマムな使い方

上記リポジトリのdeno.jsonc内部ではhttps://github.com/kawarimidoll/deno-dev-template/scriptsに定義されたスクリプトを直接呼び出すようにしています。そこの記述さえコピーすれば、ファイル自体やスクリプト本体をコピーする必要はありません。

deno.jsonc全体の説明は後に回し、ここではgit hooksとlint-stagedを使う最小のdeno.jsoncのサンプルを示します。

deno.jsonc
{
  "lint-staged": {
    "*": "deno lint && deno fmt"
  },
  "hooks_dir": ".my_hooks", // optional, default is ".hooks"
  "tasks": {
    "setup-hooks": "deno run --allow-read --allow-run https://pax.deno.dev/kawarimidoll/deno-dev-template/scripts/setup-hooks.ts",
    "pre-commit": "deno run --allow-read --allow-env --allow-run --allow-write https://pax.deno.dev/kawarimidoll/deno-dev-template/scripts/lint-staged.ts"
  }
}

各スクリプトは https://deno.land/x などには公開していないので、 https://pax.deno.dev を介して実行しています。

https://zenn.dev/kawarimidoll/articles/f02d2891a9634d

scripts/setup-hooks.ts

deno.jsoncに定義されたタスクの中で、git hooksの名前(pre-commitとかcommit-msgとか)を持ったタスクをgit hooksとして定義します。
この定義先のディレクトリはhooks_dirキーで定義できます。デフォルトは.hooksです。
内部でdeno_hooksを使っています。

scripts/lint-staged.ts

deno.jsonclint-stagedというキーで定義されたオブジェクトを使ってlint-stagedを実行します。
lint-staegd本体にはdeno.jsoncを読む機能はない(package.json.lintstagedrcを設定として使う)ので、これをよしなに解決するスクリプトです。
pre-commitで使うことを想定しています。

よしなポイント

deno_stdのjsoncモジュールを使い、deno.jsoncに定義されたlint-stagedキーの内容を読み出して、npmモジュールのlint-stagedに渡しています。
型チェックやエラーハンドリングを省いた素朴な実装は以下の通りです。

scripts/lint-staged.ts
import { parse } from "https://deno.land/std/jsonc/parse.ts";
import lintStaged from "npm:lint-staged";
const json = parse(await Deno.readTextFile(filename));
const config = json["lint-staged"];
const success = await lintStaged({ config });
if (success) {
  console.log("Linting was successful!");
  Deno.exit(0);
} else {
  console.error("Linting failed!");
  Deno.exit(1);
}

…が、これだけだと動作しなかったので、設定されたスクリプトをbash -c "..."で包む処理を挟んでいます。
具体的には、以下のように設定すると…

deno.jsonc
{
  "lint-staged": {
    "*": "deno lint && deno fmt"
  }
}

lint-stagedには以下の設定が渡されます。

config for lint-staged
{
  "*": "bash -c \"deno lint && deno fmt\""
}

「やってみたら動作した」のでこの処理を入れているのですが、内部まで詳しく理解しているわけではないので、なんか不具合出たらゴメンネ

更新の経緯

過去にdeno開発用テンプレートリポジトリを作っていました。

https://zenn.dev/kawarimidoll/articles/1c48c097020cbc

当時はVelociraptorを使用していました。Velociraptorはタスクランナー機能とgit hooks定義機能を併せ持っています。

https://velociraptor.run/

しかし、現在はDeno本体にタスクランナー機能が組み込まれています。

https://deno.com/manual@v1.34.2/tools/task_runner

さらに、deno_hooksというプロジェクトを発見しました。huskyのようにgit hooksを定義するディレクトリを作ってくれます。

https://github.com/Yakiyo/deno_hooks

これらを使ってテンプレートリポジトリを抜本的に(Velociraptorを使わない形に)更新しました。

https://github.com/kawarimidoll/deno-dev-template/releases/tag/2.0.1

これに伴い、以前の形式のテンプレートにはv1.0.0のタグをつけました。本記事のテンプレートはv2系(v2.0.0はバグがあったのでそれ以降の最新のもの)で利用できます。

テンプレートの説明

テンプレートには以下の設定が含まれているので、適宜ピックアップして使ってください。

deno.jsonc

以下のタスクが定義されています。

共通タスク

  • run: deno run --allow-env --allow-read --allow-net
    • プロジェクト共通のパーミッションを定義するタスクです。
    • コマンドラインや他のタスクでdeno task run ...と呼び出すことで、パーミッションの定義を省略できます。

スクリプト実行タスク

  • dev: deno task run --watch main.ts
    • main.tsをwatch実行します。
    • 開発用の実行を想定しています。
  • main: deno task run main.ts
    • main.tsを普通に実行します。
    • こちらは本番環境などで使用されるイメージです。
  • start: deno task run --watch server.ts
    • server.tsをwatch実行します。
    • server実行に非watch版がないのは、Deno Deployで動かす場合はタスク定義の有無は関係ないと考えたためです。

開発支援タスク

  • test: deno test --allow-env --allow-read --allow-net --allow-none
    • deno testを実行します。これはdeno task runと共通化できないので、同じパーミッションを書く必要があります。
    • --allow-noneを付けているので、テストが一つもない場合は成功ステータスで終了します。
  • cov: deno task test --coverage=cov_profile; deno coverage cov_profile
    • deno task testでカバレッジを作成し、それを表示します。
  • lint: deno lint --ignore=cov_profile
    • lintを実行します。
    • 前述のカバレッジのディレクトリを除外しています。
  • fmt: deno fmt --ignore=cov_profile
    • フォーマットを実行します。
    • 前述のカバレッジのディレクトリを除外しています。
  • ci: deno task lint && deno task fmt --check && deno task test
    • lint→fmt→testの順で実行します。
  • deps: deno run --allow-read --allow-write --allow-net --allow-run https://deno.land/x/udd@0.8.2/main.ts --test='deno task test' deno.jsonc $(find . -name '*.ts')
    • deno-uddで依存性の更新を行います。
    • ここではdeno.jsoncとtsファイルすべてを対象としていますが、プロジェクトによってはdeps.tsだけで良いかもしれません。特にudd@0.8.2時点ではnpmモジュールがあるとエラーを吐くのでnpmモジュールを使用しているプロジェクトでは注意が必要です。
npmモジュールがある場合のuddのエラー回避

npmモジュールの定義をnpm_deps.tsなどに切り出し、uddの対象をdeps.tsに絞ることでエラーを回避できます。

npm_deps.ts
export hoge from "npm:hoge";
deps.ts
export { assertEquals } from "https://deno.land/std/testing/asserts.ts";

// 各スクリプトはnpmかどうかを気にせずdeps.tsを参照すればOK
export * from "./npm_deps.ts";

git hooksタスク

  • setup-hooks: deno run --allow-read --allow-run https://pax.deno.dev/kawarimidoll/deno-dev-template/scripts/setup-hooks.ts
    • 前述のとおりです。deno.jsonc`の設定に基づいてhooksを定義します。
  • commit-msg: npx commitlint -x @commitlint/config-conventional -e "$1"
    • commitlintを実行します。
    • deno run npm:commitlintにしたかったのですが…うまく動きませんでした。今後解決できるかもしれませんが、今のところはここだけnpxです。
  • pre-commit: deno run --allow-read --allow-env --allow-run --allow-write https://pax.deno.dev/kawarimidoll/deno-dev-template/scripts/lint-staged.ts
    • 前述のとおりです。deno.jsoncの設定に基づいてlint-stagedを実行します。
  • pre-push: deno task ci
    • pushのタイミングでciタスクを実行します。

Workflows

以下のGitHub Actions定義が入っています。

  • ci
    • pull requestに反応して前述のdeno task ciを実行します。
  • udd
    • workflow_dispatchによって前述のdeno task depsを実行します。
    • スケジュールで定期実行するようにすると便利です(コメントアウトした状態で記載しています)。
    • 過去の記事で作っていたものを、taskを使用するように調整したものです。

https://zenn.dev/kawarimidoll/articles/c68204d248c107

おわりに

リポジトリのscripts/以下に作ったスクリプトは便利だと思うので使っていただけると嬉しいです。

https://github.com/kawarimidoll/deno-dev-template

Discussion