Powershell で複数のローカル開発サーバを一気に立ち上げる

アプリケーションを開発していて、複数の環境から成り立っているアプリケーションのローカル開発サーバをいちいち手動で立ち上げるのは面倒ですよね。

そこで PowerShell を使って複数のローカルサーバを一気に立ち上げたいと思います。

バージョンは以下です
```powershell
> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.5007
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.5007
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
```

# 内容

今回は以下のWebアプリケーションの開発を例にします。   
(実際にはあんまりないと思うのですが、例なので許して...)

- Next.js でフロントエンドを構築している
- Python/Flask で2つバックエンドを構築している
  - Virtualenv を使って個別の仮想環境を作っている

```
# ディレクトリ構成
- my-project/ 			# 全体 プロジェクトルート
  |
  +- dev-setup.ps1	# 今回作成する ps1
  |
  +- next-project/	# next.js プロジェクトルート
  +- flask-1/		# flask (1) プロジェクトルート
  +- flask-2/		# flask (2) プロジェクトルート
  +- env-flask-1/	# flask (1) の 仮想環境
  +- env-flask-2/	# flask (2) の 仮想環境
```

もちろんcliで動くものであればこれ以外の例にも応用できるので参考にしてください。

# 方針

以下のような方針でいきます。

- 複数の window で powershell を立ち上げる
  - PowerShell の `Start-Process` cmdlet を使うと複数のプロセスを作れる
- 各 window で開発サーバを立ち上げる
  - `powershell` コマンドに文字列としてサーバを立ち上げるコマンドを渡す

**=> `Start-Process powershell "サーバを立ち上げる文字列"` でサーバを一気に立ち上げよう!**

# PowerShell を使わないと...?

普通に立ち上げようとすると以下のようになると思います
```powershell
# next.js
> cd my-project/next-project; pnpm run dev;

# flask (1)
> cd my-project; ./env-flask-1/Scripts/activate; cd flask-1; python ./app.py

# flask (2)
> cd my-project; ./env-flask-2/Scripts/activate; cd flask-2; python ./app.py
```

今回はこれが
```powershell
> cd my-project; ./dev-setup.ps1
```
これだけで動くようにします!

# コードと解説

## コード
さて先にコードです。

```powershell
# dev-setup.ps1

$rootDir = $PSScriptRoot;	# dev-setup.ps1 が置かれているディレクトリの絶対パスを取得

function createCmdStr {		# それぞれの powershell に渡すコマンドを返す関数を作ります。
    param (				# 引数としてそれぞれのプロジェクトの名前(=envのsuffix) を取ります
        [validateSet("next-project","flask-1", "flask-2")]$project
    )
    $cmdstr = "-noexit "		# `-noexit` を付けることで即終了しなくなります。今回の例では不要ですが、サーバ以外なら必要になることがあります。
    $cmdstr = "$cmdstr cd $rootDir"	# 全体のルートディレクトリへ
    
    # next なら
if($project -eq "next-project"){		
       $cmdstr = "${cmdstr}; cd ${project}; pnpm run dev"	# pnpm で立ち上げ(これはお好きなように)
        return $cmdstr
    }
    # flask なら
    $cmdstr = "${cmdstr}; ${rootDir}\env-${project}\Scripts\activate.ps1; cd $project; python ./app.py;"	# お好きなように
    # 余談だが、terminal でやるときは `activate` でもOKだが、スクリプト内だと `activate.ps1` でなくてはならない
    return $cmdstr
}

# Statrt-Process で powershell を立ち上げ => 別 window に

# flask1
$cmdstr = createCmdStr("flask-1")
start-process powershell  "${cmdstr}"

# flask2
$cmdstr = createCmdStr("flask-2");
Start-Process powershell  "${cmdstr}"

# next
$cmdstr = createCmdStr("next-project")
Start-Process powershell  "${cmdstr}"

```


## 解説

上から順に

- **`$PSScriptRoot` でそのスクリプトファイルが置かれている絶対パスを取得** できます。
  - 今回 `dev-setup.ps1` を全体ルートの直下に置くことで全体ルートの絶対パスを取得しています。

- 処理を共通化するために実行文字列を作る関数を作りました
- powershell の関数では引数を取るのに `param()` を使うことが出来ますが、ここで `[ValidateSeet("a","b","c")]$val` とすることで引数を限定できます。ですが今回は全く関係ありません。(自己満足。ただし見やすくはなる)

- `powershell.exe` は `-noexit` を付けることで即終了しなくなります。前述のとおり(サーバがlistenし続けて終了しないので)今回の例では不要ですが、サーバ以外なら必要になることがあります。
  - **`-noexit` は渡すコマンドの前に置く必要があります**。
  - PowerShell 上で powershell を実行してもわかりにくいですが、`-noexit` を付けないと確かに即元のシェルに戻ってしまいます。例えば `powershell cd c:/` などとするとわかると思います。
    ```powershell
    > (pwd).path
    C:\Users\XXX
        
    > powershell cd C:/	# noexit つけないと
        
    > (pwd).path		# すぐ戻ってしまう。
    C:\Users\XXX
        
    > powershell -noexit cd C:/	# noexit つけると
        
    > (pwd).path		# とどまる!
    C:\
    ```

- 実はここでは書いていませんが、本来 `powershell.exe` に実行コマンドを渡すには `-command` パラメタの引数として文字列やスクリプトブロックで渡す必要があります。しかしなぜか書かなくても動きます。この挙動は公式ドキュメントにはないので謎です。よくわかりません。`Start-Process` 経由でなくても同様です。

- 先ほど取得した `dev-setup.ps1` のディレクトリに cd した後各プロジェクトのディレクトリに cd しましょう。もしくは virtualenv を activate しましょう。
  - 書いた通り virtualenv の activate は `Scripts/activate.ps1` にする必要があります。`Start-Process` 経由だと ps1 を補完してくれないようです。

- `Start-Process` を使うことで引数に指定したプログラムをプロセスとして実行できます。
  - **`Start-Process powershell` で PowerShell を別ウィンドウで開きます**。
  - 書式は `Start-Process [-FilePath] <string> [[-ArgumentList] <string[]>] ...` です。プロセスに引数を渡す場合は明示的に書くと `-ArgumentList <string[]>` ですが`-ArgumentList`は省略可能です。
  - alias は `start` です。
  - 余談ですが `Start "<URL>"` とすることでデフォルトブラウザでリンクを開けます。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3466460/cb3caaa7-73f0-4e6d-be8f-2ea048f43b9c.png)


無事に開くことが出来ました。

### 追記(2024-11-14)
どうやら Next.js を立ち上げた際に、いつまでも `Ready` から進まないことがあるようです。その時は `Ctrl+c` を押してみてください。

再現するときとしないときがありました。理由は残念ながら不明です。


# 終わりに

自分の力量不足のせいか、Powershell って明確でない挙動をすることが多いように感じます(上記の`-command` とか)。   
ですが確実に我々の Windows 生活を便利にしてくれるものです！ぜひ皆さんもつかいましょう!!!

# 参考文献
- ["【Start-Process】PowerShellで別プロセスを起動させる方法 | チェシャわら". https://cheshire-wara.com/powershell/ps-cmdlets/system-service/start-process/.](https://cheshire-wara.com/powershell/ps-cmdlets/system-service/start-process/) (Cited on: 2024-11-10).

- ["[Powershell]新しいウインドウでPowershellを起動する – エンジニ屋". https://sevenb.jp/wordpress/ura/2017/10/13/powershell%E6%96%B0%E3%81%97%E3%81%84%E3%82%A6%E3%82%A4%E3%83%B3%E3%83%89%E3%82%A6%E3%81%A7powershell%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B/.](https://sevenb.jp/wordpress/ura/2017/10/13/powershell%E6%96%B0%E3%81%97%E3%81%84%E3%82%A6%E3%82%A4%E3%83%B3%E3%83%89%E3%82%A6%E3%81%A7powershell%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B/) (Cited on: 2024-11-10).

- ["スクリプトフォルダーの取得 | MURA". https://www.vwnet.jp/windows/PowerShell/pwd.htm](https://www.vwnet.jp/windows/PowerShell/pwd.htm) 

- ["about_PowerShell_exe - PowerShell | Microsoft Learn". https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1.](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1) (Cited on: 2024-11-10).

- ["Start-Process (Microsoft.PowerShell.Management) - PowerShell | Microsoft Learn". https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4.](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4) (Cited on: 2024-11-10).