Often multiple related apps need to be started in concert for a product and it’s convenient to automate that for local development. If the apps are containerized then a Docker compose file, docker-compose up, docker-compose logs, and docker-compose down may be sufficient. However sometimes there may be a need to run the apps outside of containers locally or maybe when working with one or more non-containerized apps.
Recently I had a need to start 3 related .NET Core apps and an Angular website on Mac. On Windows I had a PowerShell Core script with steps like the following.
Start-Process -FilePath 'dotnet' -WorkingDirectory $somePath -ArgumentList 'run --debug' # Other dotnet run commands... # Start Angular website Start-Process -FilePath 'pwsh' -WorkingDirectory $webPath -ArgumentList '-NoProfile -Command ng serve -o'
Because PowerShell Core is cross-platform the script technically worked on Mac OS as well. There was one considerable difference however. On Windows multiple console instances are launched and outside of Windows it all happens in the current one. Starting the processes then isn’t the problem but stopping a specific one easily and cleanly (or all of them) may become one. Another issue is the stdout logs of the apps are no longer separated but mixed in and hard to follow.
I started to address this with some of these Bash techniques, running multiple commands with &, trying fg to send to the foreground, grabbing process ids with $!, waiting on termination etc.
None of those options gave me exactly what I was looking for in terms of Ctrl+C stop behavior and output control. Granted I didn’t spend long on it but what I was really after was more at the terminal level anyway. I love iTerm2 and really wanted to launch these apps in their own tabs inside a new iTerm2 window.
With that in mind I turned to automating iTerm2 itself. The iTerm2 Python API is a supported way of doing this but it felt like more work than quick use of their deprecated Applescript API. The following script makes me feel a bit dirty but hey it works and it’s only for occasional local dev machine use.
#!/bin/sh root_path="~/root-path" auth_api_script="$root_path/auth-path/auth-script.sh" admin_api_proj="$root_path/admin-path/admin.csproj" agent_proj="$root_path/agent-path/agent.csproj" dashboard_path="$root_path/dashboard-web" osascript <<EOF tell application "iTerm" set newWindow to (create window with default profile) tell current session of newWindow set name to "Auth API" write text "pushd $root_path && $auth_api_script" end tell tell newWindow set newTab to (create tab with default profile) tell current session of newTab set name to "Admin API" write text "dotnet run --debug -p $admin_api_proj" end tell end tell tell newWindow set newTab to (create tab with default profile) tell current session of newTab set name to "Agent" write text "dotnet run --debug -p $agent_proj" end tell end tell tell newWindow set newTab to (create tab with default profile) tell current session of newTab set name to "Dashboard" write text "pushd $dashboard_path; ng serve -o" end tell end tell end tell EOF
- Originally I had tried
create tab with profile "Default" command "command"
but there was some vague error. - For the tab title to work, the iTerm2 Title setting may have to be adjusted in Settings under Profiles\General.
Running the script starts the 4 apps in a new iTerm2 window with each in its own labeled tab where it’s easy to see specific app output and Ctrl+C stop it.