😽

モノレポの手癖を deno で CLI ツールを作って楽にしたい

2023/09/26に公開

deno で CLI ツールを作っていたら楽しくなって色々作っていた。

https://github.com/mizchi/wsr

https://github.com/mizchi/repo-fetcher

課題: モノレポの諸々の操作がだるい

npm/pnpm/yarn の workspace を使っていると、次のようなディレクトリ移動が段々面倒になってくる。

foo を build して bar を build してルートから bar のテストを流す、みたいなことをするとこういう感じになる。

$ cd packages/foo
$ pnpm build

$ cd ../bar
$ pnpm build

$ cd ../..

## コマンドの中身を確認
$ cat package.json | jq ".scripts"
{
  "test": "pnpm test:foo && pnpm test:bar",
  "test:foo": "cd packages/foo && pnpm test",
  "test:bar": "cd packages/bar && pnpm test"
}
$ pnpm test:bar

もちろん pnpm run --filter foonpm -w packages/foo でもいいが、頻度が多いのでだるい。

  • npm run -w が pnpm だと非互換で pnpm run --filter とする必要があり、使い分けが面倒
  • 普段 pnpm を使っているのだが、単純に pnpm run --filter=pkg はタイプ数が多くでだるい
  • package.json の中身を見てコマンドの中身を確認するのが面倒臭いので、さっと確認したい

という自分の手癖を解決するための CLI ツールを deno でさっと作った。

wsr

(WorkSpace Run のつもり)

$ deno install -Af https://raw.githubusercontent.com/mizchi/wsr/main/wsr.ts

# 実行
$ wsr foo build
$ wsr bar build
$ wsr test:bar

# タスク名を省略した場合 npm scripts の一覧を確認
$ wsr    
📦 bar [@pkg/bar] <root>/packages/bar
  test     $ exit 0
📦 foo [@pkg/foo] <root>/packages/foo
  test     $ exit 0
📦 root [example] <root>/
  test     $ pnpm test:foo && pnpm test:bar
  test:foo $ cd packages/foo && pnpm test
  test:bar $ cd packages/bar && pnpm test

# foo だけ確認
$ wsr foo
📦 foo [@pkg/foo] <root>/packages/foo
  test $ exit 0

(TODO: 気が向いたら deno.land/x に投げる)

モジュール指定は、次のいずれかのパターン。

  • パッケージ名(eg. @mizchi/foo)
  • モジュールのユニークなディレクトリ名(eg. foo)。ディレクトリ名が重複している場合は指定不可
  • . からはじまる相対パス(eg. ./packages/foo)
  • ルートからの相対パス(packages/foo)
  • 上記のいずれのモジュール名も満たさないルートの npm-scripts (eg. test) のコマンド名の場合、それを実行

これはモジュール側からも実行できる。

$ cd packages/bar # 移動
$ wsr foo build # bar から foo を実行
$ wsr bar test # ws ../bar test も可
$ wsr test:bar # bar にコマンドがない場合は root の npm-scripts を実行
$ wsr root test:bar # 明示的に root を指定して実行

せっかく deno で作ったので、モジュール内に deno.jsonc を含むディレクトリがあるとき、 deno task も同じコマンド体系で実行できるようにした。

$$ tree .                          
.
├── package.json
├── packages
│   └── foo
│       └── package.json
└── tasks
    └── deno.jsonc
$$ wsr           
📦 foo [@pkg/foo] <root>/packages/foo
  test $ exit 0
📦 root <root>/
  test $ echo 1
🦕 tasks <root>/tasks
  test $ echo deno

$$ wsr tasks test

$ cd /Users/kotaro.chikuba/mizchi/wsr/test/mixed-fixture/tasks
$ deno task test 
Task test echo deno
deno

これでリポジトリ内で部分的に deno+zx でタスクを書くのが簡単になる。

repo-fetcher

次に簡易 ghq + degit 的なものを作った。何も考えずに git url を叩くと、手元に clone してくれるやつ。

https://github.com/mizchi/repo-fetcher

$ deno install -Af https://raw.githubusercontent.com/mizchi/repo-fetcher/main/repo.ts
$ repo github/SpoonKnife # ~/repo/github/SpoonKnife

そのままだと repo という主語が大きいコマンドが入る。気に食わない場合は deno install -Af --name repo-fetcher ... でコマンド名を変更可。

次の設定で自分がオーナーのリポジトリは ~/mizchi/<repo> に clone するようにできる。。

~/.profile
export REPO_FETCHER_ROOT=$HOME/repo # default
export REPO_FETCHER_OWNER=mizchi
export REPO_FETCHER_OWNER_ROOT=$HOME/mizchi

ついでに degit 的な部分チェックアウトも実装した。

$ repo https://github.com/mizchi/monorepo/tree/main/packages/lib-base packages/mylib

一旦 tmpdir に突っ込んで git sparse-checkout して該当パスをローカルに mv するという実装なので、 .git を持てないのだが、雑に手元にもってきたい用なので気にならない(はず)。

テンプレート集

repo-fetcher 作ったし、せっかくなので部分チェックアウトして使う自分用のモノレポ用テンプレートも作った。

https://github.com/mizchi/monorepo

node library, CLI, React component library, React Application のテンプレートを置いている。

おまけ: deno で雑にコマンドをでっちあげる

自分は ~/bin にパスを通して雑なコマンドを置いている。ここに zx の shebang を設定したスクリプトを置く。

#!/usr/bin/env -S deno run -A --ext ts
import { $ } from "npm:zx@7.1.1";
await $`ls`;

拡張子を省略する場合、 --ext ts を付けないと js としてパースされてしまい、TS の構文がパースエラーになってしまう。

bash で制御構文を書きたくないので、最近はちょっとしたものは deno + zx で書くようになった。 deno ならインストール作業不要で動くので取り回しがいい。

実行権限を付けて実行。

$ code ~/bin/mycli # ↑ を編集
$ chmod +x ~/bin/mycli
$ mycli

deno install -Af mycli.ts~/.deno/bin の下に mycli をインストールすることもできるが、これだと修正するたびにインストールが必要でちょっと面倒。雑に手元でいじりながら処理を書く場合はこっちのが楽。

感想

bash は deno + zx は CLI 作るのに取り回しがよくて最高。

Discussion