はじめに
フルスタックアプリケーションを開発していると、次のように複数のディレクトリを持つ構成がよくあります。
my-app/
├── frontend/
├── backend/
└── worker/
このようなプロジェクトでは、開発を始めるたびに複数のターミナルを開き、それぞれのディレクトリへ移動してコマンドを実行する必要があります。
cd frontend
npm run devcd backend
go run .cd worker
python main.pyシェルスクリプトやMakefileを使えばまとめることはできます。しかし、並列実行、環境変数、実行対象の絞り込み、ログの整理、Windows対応まで考え始めると、少しずつ管理が複雑になります。
一方で、NxやTurborepoのようなモノレポ向けツールは強力ですが、ビルドキャッシュやパッケージ依存グラフまでは必要ないプロジェクトもあります。
そこで、複数ディレクトリのコマンド実行に焦点を当てた、Go製の軽量タスクランナー「bnm」を作りました。
GitHubリポジトリはこちらです。
https://github.com/Yarnadori/bnm
bnmとは
bnmは、複数のディレクトリで構成されたプロジェクト向けのタスクランナーです。
フロントエンド、バックエンド、ワーカー、バッチ処理など、ディレクトリごとに異なるコマンドを、ひとつの設定ファイルから実行できます。
主な機能は次のとおりです。
- 複数タスクの並列実行
- タスクの逐次実行
- スクリプト間の依存関係
- 実行するディレクトリの絞り込み
- コマンドへの追加引数の受け渡し
- 最大同時実行数の設定
- プロジェクトルートと各ディレクトリの
.env読み込み - ディレクトリ名付きの色分けされたログ
- Windows、macOS、Linuxへの対応
- Bash、Zsh、Fish向けのシェル補完
- Ctrl+Cによる子プロセスの一括終了
bnmはビルドシステムではありません。
複雑なキャッシュや依存関係の自動解析を行うのではなく、複数ディレクトリにまたがるコマンド実行を、分かりやすく管理することを目的としています。
インストール
LinuxまたはmacOSでは、次のコマンドでインストールできます。
curl -fsSL https://raw.githubusercontent.com/Yarnadori/bnm/main/install.sh | bashWindowsでは、PowerShellからインストールできます。
irm https://raw.githubusercontent.com/Yarnadori/bnm/main/install.ps1 | iexGitHub Releasesから、使用しているOSに対応したバイナリを直接ダウンロードすることもできます。
Goがインストールされている場合は、ソースコードからビルドできます。
git clone https://github.com/Yarnadori/bnm.git
cd bnm
go build -o bnm .bnmを使ってみる
ここからは、フロントエンドとバックエンドを持つプロジェクトを例に、基本的な使い方を紹介します。
プロジェクトの構成は次のとおりです。
my-app/
├── frontend/
├── backend/
└── README.mdまず、プロジェクトのルートディレクトリで初期化コマンドを実行します。
bnm initbnmはサブディレクトリを検出し、プロジェクトルートにbnm.jsonを作成します。
生成される設定は、次のような形式です。
{
"name": "my-app",
"version": "0.0.0",
"directories": {
"BACKEND": {
"alias": "B",
"path": "./backend"
},
"FRONTEND": {
"alias": "F",
"path": "./frontend"
}
},
"scripts": {}
}
directoriesには、bnmがコマンドを実行するディレクトリを定義します。
それぞれのディレクトリには、名前、パス、短いエイリアスが設定されます。
開発コマンドを定義する
次に、フロントエンドとバックエンドを同時に起動するdevスクリプトを追加します。
{
"name": "my-app",
"version": "1.0.0",
"directories": {
"BACKEND": {
"alias": "B",
"path": "./backend"
},
"FRONTEND": {
"alias": "F",
"path": "./frontend"
}
},
"scripts": {
"dev": {
"mode": "parallel",
"tasks": [
{
"dir": "FRONTEND",
"command": "npm run dev"
},
{
"dir": "BACKEND",
"command": "go run ."
}
]
}
}
}
modeにparallelを指定すると、登録したタスクが並列で実行されます。
設定後、次のコマンドを実行します。
bnm devこれだけで、フロントエンドとバックエンドの開発サーバーを同時に起動できます。
それぞれの標準出力にはディレクトリ名が付くため、どのプロセスが出力したログなのかを確認しやすくなっています。
FRONTEND ready on http://localhost:3000
BACKEND server started on http://localhost:8080終了するときはCtrl+Cを押します。
bnmが起動したプロセスをまとめて終了するため、個別のターミナルを探して停止する必要はありません。
タスクを順番に実行する
ビルド処理など、タスクを決められた順番で実行したい場合は、modeにsequentialを指定します。
{
"scripts": {
"build": {
"mode": "sequential",
"tasks": [
{
"dir": "FRONTEND",
"command": "npm run build"
},
{
"dir": "BACKEND",
"command": "go build ./..."
}
]
}
}
}
次のコマンドで実行できます。
bnm build逐次実行では、先に登録したタスクが完了してから次のタスクが実行されます。
途中のタスクが失敗した場合は、その時点で実行を停止します。終了コードも0以外になるため、ローカル開発だけでなくCIでも利用できます。
実行するディレクトリを絞り込む
すべてのサービスを起動する必要がない場合は、ディレクトリを指定して一部のタスクだけを実行できます。
たとえば、フロントエンドだけを起動する場合は、次のように指定します。
bnm dev -F-Fは、FRONTENDに設定されたエイリアスです。
ディレクトリ名を直接指定することもできます。
bnm dev FRONTENDパスによる指定にも対応しています。
bnm dev ./frontend複数のディレクトリを指定する場合は、続けて記述します。
bnm dev FRONTEND BACKEND開発中に特定のサービスだけを再起動したい場合や、一部のテストだけを実行したい場合に便利です。
コマンドへ追加の引数を渡す
スクリプトに一時的なオプションを追加したい場合は、--以降に引数を指定します。
bnm dev -F -- --port 3000この場合、--port 3000がフロントエンドのコマンドに追加されます。
テストを監視モードで実行する場合は、次のように指定できます。
bnm test -- --watch設定ファイルを変更せず、その場でオプションを追加できるため、開発中の一時的な実行に利用できます。
スクリプト間に依存関係を設定する
デプロイ前に必ずビルドを実行したい場合などは、dependsOnを使用します。
{
"scripts": {
"build": {
"tasks": [
{
"dir": "FRONTEND",
"command": "npm run build"
}
]
},
"deploy": {
"dependsOn": ["build"],
"tasks": [
{
"dir": "BACKEND",
"command": "npm run deploy"
}
]
}
}
}
次のコマンドを実行すると、最初にbuildが実行され、正常に完了した後でdeployが実行されます。
bnm deploy依存するスクリプトが失敗した場合、後続のスクリプトは実行されません。
また、循環した依存関係はエラーとして扱われます。
任意のコマンドを実行する
あらかじめbnm.jsonに登録していないコマンドも、bnm execを使って実行できます。
フロントエンドでパッケージをインストールする場合は、次のように実行します。
bnm exec -F npm installディレクトリ名を指定することもできます。
bnm exec FRONTEND npm installすべての登録ディレクトリで同じコマンドを実行する場合は、--allを使用します。
bnm exec --all git status次のような操作にも利用できます。
bnm exec --all npm installbnm exec --all git pullbnm exec --all go test ./...複数のリポジトリや独立したサービスをひとつの親ディレクトリで管理している場合に便利です。
環境変数を管理する
bnmは、プロジェクトルートの.envを自動的に読み込みます。
my-app/
├── .env
├── frontend/
│ └── .env
├── backend/
│ └── .env
└── bnm.json
各タスクの実行時には、対象ディレクトリ内の.envも読み込まれます。
さらに、タスクごとに環境変数を指定できます。
{
"scripts": {
"dev": {
"tasks": [
{
"dir": "BACKEND",
"command": "go run .",
"env": {
"APP_ENV": "development",
"PORT": "8080"
}
}
]
}
}
}
環境変数は、次の順番で上書きされます。
- 実行環境とプロジェクトルートの
.env - タスク対象ディレクトリの
.env - タスクの
env設定
また、プロジェクト名とバージョンは、それぞれPROJECT_NAMEとPROJECT_VERSIONとしてタスクから参照できます。
ディレクトリ構成を同期する
プロジェクトへ新しいディレクトリを追加した場合は、次のコマンドを実行します。
bnm syncbnmが現在のディレクトリ構成を確認し、bnm.jsonのdirectoriesを更新します。
既存のディレクトリに設定されたエイリアスは維持され、新しく追加されたディレクトリには新しいエイリアスが設定されます。
削除されたディレクトリは設定から取り除かれます。
手動で設定を追加し直す必要がないため、サービスが増減するプロジェクトでも管理しやすくなります。
bnmが向いているプロジェクト
bnmは、次のようなプロジェクトに向いています。
- フロントエンドとバックエンドが別ディレクトリにある
- Go、Node.js、Pythonなど複数言語を使用している
- 複数の開発サーバーを同時に起動したい
- シェルスクリプトより構造化された設定で管理したい
- NxやTurborepoほど大きな仕組みは必要ない
- 単一バイナリで導入できるツールを使いたい
- ローカルとCIで同じコマンドを使用したい
一方で、次のような機能が必要な場合は、ほかのツールが適している可能性があります。
- ビルド結果のキャッシュ
- リモートキャッシュ
- Gitの差分を使った影響範囲の解析
- パッケージ依存関係の自動解析
- 大規模モノレポ向けの分散実行
bnmは、フル機能のモノレポビルドシステムを置き換えることを目的としていません。
複数ディレクトリのコマンドをまとめたいが、大きな仕組みまでは必要ない。その間を埋めるためのツールです。
おわりに
この記事では、複数ディレクトリで構成されたプロジェクト向けのタスクランナー「bnm」を紹介しました。
bnmを使うと、フロントエンド、バックエンド、ワーカーなどのコマンドを、次のような短い操作でまとめられます。
bnm init
bnm dev
bnm build
bnm dev -F
bnm exec --all git status複数のターミナルを開いて同じ操作を繰り返している場合や、シェルスクリプトが複雑になっている場合は、bnmが選択肢のひとつになるかもしれません。
現在も機能や設定形式を改善しています。
実際に使ってみた感想、分かりにくかった点、追加してほしい機能などがあれば、GitHubのIssueやDiscussionでフィードバックをもらえるとうれしいです。
GitHub:
