やまどり
NxやTurborepoを入れるほどではない複数ディレクトリ構成を、Go製CLI「bnm」で管理する

NxやTurborepoを入れるほどではない複数ディレクトリ構成を、Go製CLI「bnm」で管理する

フロントエンドやバックエンドなど、複数ディレクトリのコマンドを一括実行できる軽量タスクランナー「bnm」を紹介します。

Share on XShare on Facebook

はじめに

フルスタックアプリケーションを開発していると、次のように複数のディレクトリを持つ構成がよくあります。

my-app/
├── frontend/
├── backend/
└── worker/

このようなプロジェクトでは、開発を始めるたびに複数のターミナルを開き、それぞれのディレクトリへ移動してコマンドを実行する必要があります。

Shell
cd frontend
npm run dev
Shell
cd backend
go run .
Shell
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では、次のコマンドでインストールできます。

Shell
curl -fsSL https://raw.githubusercontent.com/Yarnadori/bnm/main/install.sh | bash

Windowsでは、PowerShellからインストールできます。

Shell
irm https://raw.githubusercontent.com/Yarnadori/bnm/main/install.ps1 | iex

GitHub Releasesから、使用しているOSに対応したバイナリを直接ダウンロードすることもできます。

Goがインストールされている場合は、ソースコードからビルドできます。

Shell
git clone https://github.com/Yarnadori/bnm.git
cd bnm
go build -o bnm .

bnmを使ってみる

ここからは、フロントエンドとバックエンドを持つプロジェクトを例に、基本的な使い方を紹介します。

プロジェクトの構成は次のとおりです。

my-app/
├── frontend/
├── backend/
└── README.md

まず、プロジェクトのルートディレクトリで初期化コマンドを実行します。

Shell
bnm init

bnmはサブディレクトリを検出し、プロジェクトルートにbnm.jsonを作成します。

生成される設定は、次のような形式です。

bnm.json
{
  "name": "my-app",
  "version": "0.0.0",
  "directories": {
    "BACKEND": {
      "alias": "B",
      "path": "./backend"
    },
    "FRONTEND": {
      "alias": "F",
      "path": "./frontend"
    }
  },
  "scripts": {}
}

directoriesには、bnmがコマンドを実行するディレクトリを定義します。

それぞれのディレクトリには、名前、パス、短いエイリアスが設定されます。

開発コマンドを定義する

次に、フロントエンドとバックエンドを同時に起動するdevスクリプトを追加します。

bnm.json
{
  "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 ."
        }
      ]
    }
  }
}

modeparallelを指定すると、登録したタスクが並列で実行されます。

設定後、次のコマンドを実行します。

Shell
bnm dev

これだけで、フロントエンドとバックエンドの開発サーバーを同時に起動できます。

それぞれの標準出力にはディレクトリ名が付くため、どのプロセスが出力したログなのかを確認しやすくなっています。

FRONTEND  ready on http://localhost:3000
BACKEND   server started on http://localhost:8080

終了するときはCtrl+Cを押します。

bnmが起動したプロセスをまとめて終了するため、個別のターミナルを探して停止する必要はありません。

タスクを順番に実行する

ビルド処理など、タスクを決められた順番で実行したい場合は、modesequentialを指定します。

bnm.json
{
  "scripts": {
    "build": {
      "mode": "sequential",
      "tasks": [
        {
          "dir": "FRONTEND",
          "command": "npm run build"
        },
        {
          "dir": "BACKEND",
          "command": "go build ./..."
        }
      ]
    }
  }
}

次のコマンドで実行できます。

Shell
bnm build

逐次実行では、先に登録したタスクが完了してから次のタスクが実行されます。

途中のタスクが失敗した場合は、その時点で実行を停止します。終了コードも0以外になるため、ローカル開発だけでなくCIでも利用できます。

実行するディレクトリを絞り込む

すべてのサービスを起動する必要がない場合は、ディレクトリを指定して一部のタスクだけを実行できます。

たとえば、フロントエンドだけを起動する場合は、次のように指定します。

Shell
bnm dev -F

-Fは、FRONTENDに設定されたエイリアスです。

ディレクトリ名を直接指定することもできます。

Shell
bnm dev FRONTEND

パスによる指定にも対応しています。

Shell
bnm dev ./frontend

複数のディレクトリを指定する場合は、続けて記述します。

Shell
bnm dev FRONTEND BACKEND

開発中に特定のサービスだけを再起動したい場合や、一部のテストだけを実行したい場合に便利です。

コマンドへ追加の引数を渡す

スクリプトに一時的なオプションを追加したい場合は、--以降に引数を指定します。

Shell
bnm dev -F -- --port 3000

この場合、--port 3000がフロントエンドのコマンドに追加されます。

テストを監視モードで実行する場合は、次のように指定できます。

Shell
bnm test -- --watch

設定ファイルを変更せず、その場でオプションを追加できるため、開発中の一時的な実行に利用できます。

スクリプト間に依存関係を設定する

デプロイ前に必ずビルドを実行したい場合などは、dependsOnを使用します。

bnm.json
{
  "scripts": {
    "build": {
      "tasks": [
        {
          "dir": "FRONTEND",
          "command": "npm run build"
        }
      ]
    },
    "deploy": {
      "dependsOn": ["build"],
      "tasks": [
        {
          "dir": "BACKEND",
          "command": "npm run deploy"
        }
      ]
    }
  }
}

次のコマンドを実行すると、最初にbuildが実行され、正常に完了した後でdeployが実行されます。

Shell
bnm deploy

依存するスクリプトが失敗した場合、後続のスクリプトは実行されません。

また、循環した依存関係はエラーとして扱われます。

任意のコマンドを実行する

あらかじめbnm.jsonに登録していないコマンドも、bnm execを使って実行できます。

フロントエンドでパッケージをインストールする場合は、次のように実行します。

Shell
bnm exec -F npm install

ディレクトリ名を指定することもできます。

Shell
bnm exec FRONTEND npm install

すべての登録ディレクトリで同じコマンドを実行する場合は、--allを使用します。

Shell
bnm exec --all git status

次のような操作にも利用できます。

Shell
bnm exec --all npm install
Shell
bnm exec --all git pull
Shell
bnm exec --all go test ./...

複数のリポジトリや独立したサービスをひとつの親ディレクトリで管理している場合に便利です。

環境変数を管理する

bnmは、プロジェクトルートの.envを自動的に読み込みます。

my-app/
├── .env
├── frontend/
│   └── .env
├── backend/
│   └── .env
└── bnm.json

各タスクの実行時には、対象ディレクトリ内の.envも読み込まれます。

さらに、タスクごとに環境変数を指定できます。

bnm.json
{
  "scripts": {
    "dev": {
      "tasks": [
        {
          "dir": "BACKEND",
          "command": "go run .",
          "env": {
            "APP_ENV": "development",
            "PORT": "8080"
          }
        }
      ]
    }
  }
}

環境変数は、次の順番で上書きされます。

  1. 実行環境とプロジェクトルートの.env
  2. タスク対象ディレクトリの.env
  3. タスクのenv設定

また、プロジェクト名とバージョンは、それぞれPROJECT_NAMEPROJECT_VERSIONとしてタスクから参照できます。

ディレクトリ構成を同期する

プロジェクトへ新しいディレクトリを追加した場合は、次のコマンドを実行します。

Shell
bnm sync

bnmが現在のディレクトリ構成を確認し、bnm.jsondirectoriesを更新します。

既存のディレクトリに設定されたエイリアスは維持され、新しく追加されたディレクトリには新しいエイリアスが設定されます。

削除されたディレクトリは設定から取り除かれます。

手動で設定を追加し直す必要がないため、サービスが増減するプロジェクトでも管理しやすくなります。

bnmが向いているプロジェクト

bnmは、次のようなプロジェクトに向いています。

  • フロントエンドとバックエンドが別ディレクトリにある
  • Go、Node.js、Pythonなど複数言語を使用している
  • 複数の開発サーバーを同時に起動したい
  • シェルスクリプトより構造化された設定で管理したい
  • NxやTurborepoほど大きな仕組みは必要ない
  • 単一バイナリで導入できるツールを使いたい
  • ローカルとCIで同じコマンドを使用したい

一方で、次のような機能が必要な場合は、ほかのツールが適している可能性があります。

  • ビルド結果のキャッシュ
  • リモートキャッシュ
  • Gitの差分を使った影響範囲の解析
  • パッケージ依存関係の自動解析
  • 大規模モノレポ向けの分散実行

bnmは、フル機能のモノレポビルドシステムを置き換えることを目的としていません。

複数ディレクトリのコマンドをまとめたいが、大きな仕組みまでは必要ない。その間を埋めるためのツールです。

おわりに

この記事では、複数ディレクトリで構成されたプロジェクト向けのタスクランナー「bnm」を紹介しました。

bnmを使うと、フロントエンド、バックエンド、ワーカーなどのコマンドを、次のような短い操作でまとめられます。

Shell
bnm init
bnm dev
bnm build
bnm dev -F
bnm exec --all git status

複数のターミナルを開いて同じ操作を繰り返している場合や、シェルスクリプトが複雑になっている場合は、bnmが選択肢のひとつになるかもしれません。

現在も機能や設定形式を改善しています。

実際に使ってみた感想、分かりにくかった点、追加してほしい機能などがあれば、GitHubのIssueやDiscussionでフィードバックをもらえるとうれしいです。

GitHub:

https://github.com/Yarnadori/bnm