Denoでもgit hooksでlint-stagedする!deno.jsonc時代の開発テンプレート
tl;dr
拙作deno-dev-templateを更新しました。
テンプレートをそのまま使っても良いですが、deno.jsonc
をローカルのdenoプロジェクトにコピーするだけでも便利タスクが使えます。
deno task setup-hooks
を実行すると、git hooksが定義され、lint-stagedとcommitlint(npx使用)が動くようになります。
ミニマムな使い方
上記リポジトリのdeno.jsonc
内部ではhttps://github.com/kawarimidoll/deno-dev-template/scripts
に定義されたスクリプトを直接呼び出すようにしています。そこの記述さえコピーすれば、ファイル自体やスクリプト本体をコピーする必要はありません。
deno.jsonc
全体の説明は後に回し、ここではgit hooksとlint-stagedを使う最小の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 を介して実行しています。
scripts/setup-hooks.ts
deno.jsonc
に定義されたタスクの中で、git hooksの名前(pre-commitとかcommit-msgとか)を持ったタスクをgit hooksとして定義します。
この定義先のディレクトリはhooks_dir
キーで定義できます。デフォルトは.hooks
です。
内部でdeno_hooksを使っています。
scripts/lint-staged.ts
deno.jsonc
にlint-staged
というキーで定義されたオブジェクトを使ってlint-stagedを実行します。
lint-staegd本体にはdeno.jsonc
を読む機能はない(package.json
や.lintstagedrc
を設定として使う)ので、これをよしなに解決するスクリプトです。
pre-commitで使うことを想定しています。
よしなポイント
deno_stdのjsoncモジュールを使い、deno.jsonc
に定義されたlint-staged
キーの内容を読み出して、npmモジュールのlint-stagedに渡しています。
型チェックやエラーハンドリングを省いた素朴な実装は以下の通りです。
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 "..."
で包む処理を挟んでいます。
具体的には、以下のように設定すると…
{
"lint-staged": {
"*": "deno lint && deno fmt"
}
}
lint-stagedには以下の設定が渡されます。
{
"*": "bash -c \"deno lint && deno fmt\""
}
「やってみたら動作した」のでこの処理を入れているのですが、内部まで詳しく理解しているわけではないので、なんか不具合出たらゴメンネ
更新の経緯
過去にdeno開発用テンプレートリポジトリを作っていました。
当時はVelociraptorを使用していました。Velociraptorはタスクランナー機能とgit hooks定義機能を併せ持っています。
しかし、現在はDeno本体にタスクランナー機能が組み込まれています。
さらに、deno_hooksというプロジェクトを発見しました。huskyのようにgit hooksを定義するディレクトリを作ってくれます。
これらを使ってテンプレートリポジトリを抜本的に(Velociraptorを使わない形に)更新しました。
これに伴い、以前の形式のテンプレートには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
に絞ることでエラーを回避できます。
export hoge from "npm:hoge";
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
を実行します。
- pull requestに反応して前述の
- udd
- workflow_dispatchによって前述の
deno task deps
を実行します。 - スケジュールで定期実行するようにすると便利です(コメントアウトした状態で記載しています)。
- 過去の記事で作っていたものを、taskを使用するように調整したものです。
- workflow_dispatchによって前述の
おわりに
リポジトリのscripts/
以下に作ったスクリプトは便利だと思うので使っていただけると嬉しいです。
Discussion