Last active
February 27, 2026 07:35
-
-
Save rmpel/f5c220b5e49baf0df4f5bbf5b7e76fdf to your computer and use it in GitHub Desktop.
zsh automatic LocalWP context switcher when navigating folders of projects on MacOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # this is a zsh script to automatically switch to the localwp shell when you enter a | |
| # directory that contains a LocalWP website installation | |
| # it is designed to trigger on any directory deeper or exactly the app/ folder of a localwp site. | |
| # this code is FAR FROM perfect, but it works! Feel free to suggest/improve/etc | |
| # | |
| # this file is to be sourced in your .zshrc . | |
| # | |
| # Suggested install method: | |
| # cd ~ ; mkdir -p .zsh ; git clone https://gist.github.com/f5c220b5e49baf0df4f5bbf5b7e76fdf.git .zsh/zsh-auto-localwp | |
| # echo "source ~/.zsh/zsh-auto-localwp/zsh-auto-localwp.sh" >> ~/.zshrc | |
| # | |
| # Some software is required apart from tools you should already have on your mac; | |
| # Prereq; LocalWP (required, of course ;) ) | |
| # https://localwp.com/ | |
| # Prereq; jq (required) | |
| # Linux: sudo apt install jq | |
| # MacOS: brew install jq | |
| # If you do not already have brew, please go to https://brew.sh/ for installation instructions. | |
| # Prereq; local-cli (optional) | |
| # npm install -g @getflywheel/local-cli | |
| # WARNING! local-cli is no longer maintained, if unavailable, you can install from my github. This is convoluted, and I am | |
| # probably doing it wrong, but this worked; | |
| # npm install -g https://github.com/rmpel/local-cli.git | |
| # npm list -g --depth=0 | grep local-cli | |
| # echo 'Find the clone path, see output above, for example ├── @getflywheel/local-cli@0.0.5 -> ./../../../../../.npm/_cacache/tmp/git-cloneJZTRb5' | |
| # echo 'If the ~/.npm/_cacache/tmp/git-cloneJZTRb5/bin folder is missing or empty, do this; | |
| # rm -rf ~/.npm/_cacache/tmp/git-cloneJZTRb5 && git clone https://github.com/rmpel/local-cli.git ~/.npm/_cacache/tmp/git-cloneJZTRb5 | |
| # | |
| # Prereq; realpath, sed, grep | |
| # | |
| # Method of operation: | |
| # 1. When you enter a directory that matches a pattern ....../a-name/app/..... it will assume the sitename is a-name, and the site path is ....../a-name | |
| # 2. It will look up the site id in the localwp sites.json file. If this path is listed, it will use the site id to set the environment. | |
| # 3. If local-cli is available, it will check if the site is running, and start it if it is not. | |
| # 4. When the environment is set, this will not be done again until you leave the site directory. | |
| # 5. When you leave the site directory, the environment will be unset. You can jump between sites without any issues and each terminal will have their own environment. | |
| # | |
| # Notes; | |
| # Script is far from DRY. Optimisation is needed. | |
| # Script is far from perfect. It works, but it can and should be improved. | |
| # Only developed and tested for MacOS using the Apple Terminal emulator. | |
| # And for linux, MX Linux, xfce4 Terminal. | |
| # And it goes without saying; only for zsh. | |
| # | |
| # This script is provided as-is, without any warranty or guarantee. Use at your own risk. | |
| # Feel free to use, modify, share, etc. If you have improvements, please share them back. | |
| # | |
| # License; GPL v3, see https://www.gnu.org/licenses/gpl-3.0.html share-alike, attribution required. | |
| ### | |
| # Global configuration; | |
| # Define in your environment before loading this add-on. | |
| # export AUTO_START_LOCALWP_APP=yes | |
| AUTO_START_LOCALWP_APP=${AUTO_START_LOCALWP_APP:-no} | |
| ZSH_AUTO_LOCALWP_PATH=$0:P | |
| LOCAL_RUNTIME_DIRECTORY=~/.config/zsh-localwp/runtime | |
| LOCAL_APP_LOCATION=~/.config/zsh-localwp/binary | |
| LOCAL_APP_RESOURCES=~/.config/zsh-localwp/resources | |
| LOCAL_CLI=$(which local-cli | head -n 1) | |
| NOHUP=$(which setsid nohup | head -n 1) | |
| LOCAL_SYSTEM_ARCHITECTURE= | |
| ### | |
| # Get architecture | |
| # returns linux, arm64 for macos on arm, x86_64 for macos on intel. Windows WSL support not yet here. Please contribute. | |
| # @return string | |
| function localwp_get_arch { | |
| [ "" != "$LOCAL_SYSTEM_ARCHITECTURE" ] && echo "$LOCAL_SYSTEM_ARCHITECTURE" && return 0 | |
| [ "" != "$(which arch)" ] && LOCAL_SYSTEM_ARCHITECTURE=darwin && [ "arm64" = "$(arch)" ] && LOCAL_SYSTEM_ARCHITECTURE=darwin-arm64 | |
| [ -d /opt/Local/resources ] && LOCAL_SYSTEM_ARCHITECTURE=linux | |
| echo "$LOCAL_SYSTEM_ARCHITECTURE" | |
| } | |
| LOCAL_SYSTEM_ARCHITECTURE=$(localwp_get_arch) | |
| # function _localwp_screenresize { | |
| # printf '\e[1;%dr' $((LINES-1)) # set scrolling region (rows 1..LINES-1) | |
| # } | |
| # trap _localwp_screenresize SIGWINCH | |
| # _localwp_screenresize | |
| ### | |
| # Wrapper function for local-cli calls that handles Node version switching | |
| # This ensures local-cli is available even when the user has switched Node versions | |
| # @param $@ All arguments to pass to local-cli | |
| # @return Exit code from local-cli | |
| function _localwp_safe_cli_call { | |
| local EXIT_CODE | |
| local ORIGINAL_NODE="" | |
| local NODE_SWITCHED=0 | |
| # Check if local-cli is available | |
| [ ! -e "$LOCAL_CLI" ] && return 1 | |
| # Detect and handle nvm (Node Version Manager) | |
| if [ -n "$NVM_DIR" ] && [ -s "$NVM_DIR/nvm.sh" ]; then | |
| # Source nvm if not already loaded | |
| if ! command -v nvm >/dev/null 2>&1; then | |
| source "$NVM_DIR/nvm.sh" >/dev/null 2>&1 | |
| fi | |
| if command -v nvm >/dev/null 2>&1; then | |
| ORIGINAL_NODE=$(nvm current 2>/dev/null) | |
| # Only switch if we're not already on system or default | |
| if [ "$ORIGINAL_NODE" != "system" ] && [ "$ORIGINAL_NODE" != "default" ]; then | |
| nvm use default >/dev/null 2>&1 || nvm use system >/dev/null 2>&1 | |
| NODE_SWITCHED=1 | |
| fi | |
| fi | |
| # Detect and handle fnm (Fast Node Manager) | |
| elif command -v fnm >/dev/null 2>&1; then | |
| ORIGINAL_NODE=$(node -v 2>/dev/null) | |
| # Try to switch to default | |
| if fnm use default >/dev/null 2>&1 || fnm use system >/dev/null 2>&1; then | |
| NODE_SWITCHED=1 | |
| fi | |
| fi | |
| # Execute the local-cli command | |
| "$LOCAL_CLI" "$@" | |
| EXIT_CODE=$? | |
| # Restore original Node version if we switched | |
| if [ $NODE_SWITCHED -eq 1 ] && [ -n "$ORIGINAL_NODE" ]; then | |
| if [ -n "$NVM_DIR" ] && command -v nvm >/dev/null 2>&1; then | |
| nvm use "$ORIGINAL_NODE" >/dev/null 2>&1 | |
| elif command -v fnm >/dev/null 2>&1; then | |
| fnm use "$ORIGINAL_NODE" >/dev/null 2>&1 | |
| fi | |
| fi | |
| return $EXIT_CODE | |
| } | |
| ### Start site in background and use polling to check if running. | |
| # We do this because very often local-cli start-site will hang, even if the site is successfully started. This is a workaround for that issue. | |
| # polling ends when either the background tasks is ended or when the local-cli is responsive for the site. | |
| # $1 is the site id. | |
| function _localwp_polling_start_site { | |
| local SITE_ID="$1" | |
| # Start the site in the background | |
| _localwp_safe_cli_call start-site "$SITE_ID" >/dev/null 2>&1 & | |
| local START_PID=$! | |
| echo -n "Starting site (PID: $START_PID)..." >&2 | |
| # Poll until either the background process ends or the site is running | |
| local MAX_WAIT=60 # Maximum wait time in seconds | |
| local ELAPSED=0 | |
| while [ $ELAPSED -lt $MAX_WAIT ]; do | |
| # Check if the background process is still running | |
| if ! kill -0 "$START_PID" 2>/dev/null; then | |
| echo " process ended." >&2 | |
| break | |
| fi | |
| # Check if the site is now running | |
| local SITE_STATE=$(localwp_get_nth_word 6 $(_localwp_safe_cli_call list-sites 2>/dev/null | grep -- "$SITE_ID")) | |
| if [ "$SITE_STATE" = "running" ]; then | |
| echo " site is running!" >&2 | |
| # Kill the hanging start-site process since the site is already running | |
| kill "$START_PID" 2>/dev/null | |
| wait "$START_PID" 2>/dev/null | |
| return 0 | |
| fi | |
| # Wait briefly before checking again | |
| sleep 1 | |
| ELAPSED=$((ELAPSED + 1)) | |
| echo -n "." >&2 | |
| done | |
| # If we reached the timeout, warn the user | |
| if [ $ELAPSED -ge $MAX_WAIT ]; then | |
| echo " timeout reached!" >&2 | |
| # Kill the process if it's still running | |
| if kill -0 "$START_PID" 2>/dev/null; then | |
| kill "$START_PID" 2>/dev/null | |
| wait "$START_PID" 2>/dev/null | |
| fi | |
| return 1 | |
| fi | |
| # Wait for the background process to fully exit | |
| wait "$START_PID" 2>/dev/null | |
| return 0 | |
| } | |
| ### Create or update symlinks to the LocalWP runtime directory, Application Resources, and LocalWP binary. | |
| # We do this to create a uniform environment for all platforms. | |
| # | |
| function _localwp_symlink_maintenance { | |
| [ ! -d ~/.config/zsh-localwp ] && mkdir -p ~/.config/zsh-localwp | |
| [ -L "$LOCAL_RUNTIME_DIRECTORY" ] && rm "$LOCAL_RUNTIME_DIRECTORY" | |
| [ -L "$LOCAL_APP_LOCATION" ] && rm "$LOCAL_APP_LOCATION" | |
| [ -L "$LOCAL_APP_RESOURCES" ] && rm "$LOCAL_APP_RESOURCES" | |
| local APP_LOCATION | |
| case $LOCAL_SYSTEM_ARCHITECTURE in | |
| darwin-arm64|darwin) | |
| # create symlinks; MacOS edition. | |
| APP_LOCATION="$(ls -1d {,~}/Applications/Local.app 2>/dev/null | grep Applications -m 1)" | |
| [ -e ~/Library/Application" "Support/Local ] && ln -s ~/Library/Application" "Support/Local "$LOCAL_RUNTIME_DIRECTORY" | |
| [ -e "$APP_LOCATION" ] && ln -s "$APP_LOCATION" "$LOCAL_APP_LOCATION" | |
| [ -e "$APP_LOCATION/Contents/Resources" ] && ln -s "$APP_LOCATION/Contents/Resources" "$LOCAL_APP_RESOURCES" | |
| ;; | |
| linux) | |
| # create symlinks; Linux edition. | |
| [ -e ~/.config/Local ] && ln -s /opt/Local "$LOCAL_RUNTIME_DIRECTORY" | |
| [ -e /opt/Local/local ] && ln -s /opt/Local/local "$LOCAL_APP_LOCATION" | |
| [ -e /opt/Local/resources ] && ln -s /opt/Local/resources "$LOCAL_APP_RESOURCES" | |
| ;; | |
| esac | |
| # PolyFill; the local-cli has hardcoded paths to the MacOS runtime directory. | |
| local LOCAL_RUNTIME=~/.config/Local | |
| local LOCAL_RUNTIME_MACOS=~/Library/Application" "Support/Local | |
| [ -d $LOCAL_RUNTIME ] && [ ! -d "$LOCAL_RUNTIME_MACOS" ] && echo Non-MacOS path detected. Local-CLI expects it. Creating symlinks. Please hold... >&2 && mkdir -p ~/Library/Application\ Support && ln -s ~/.config/Local ~/Library/Application\ Support/ && echo Done. >&2 | |
| } | |
| _localwp_symlink_maintenance | |
| [ ! -d "$LOCAL_RUNTIME_DIRECTORY" ] && echo LocalWP Runtime Dir not found. Inspect code, fix, try again. && return; | |
| ### | |
| # Get the basename of a path, without using the basename command. | |
| # @param $1 Path to the file or directory | |
| # @param $2 Suffix to remove from the basename (optional) | |
| _localwp_basename() { | |
| # Usage: basename "path" ["suffix"] | |
| # Strip all trailing forward-slashes '/' from | |
| # the end of the string. | |
| # | |
| # "${1##*[!/]}": Remove all non-forward-slashes | |
| # from the start of the string, leaving us with only | |
| # the trailing slashes. | |
| # "${1%%"${}"}: Remove the result of the above | |
| # substitution (a string of forward slashes) from the | |
| # end of the original string. | |
| dir=${1%${1##*[!/]}} | |
| # Remove everything before the final forward-slash '/'. | |
| dir=${dir##*/} | |
| # If a suffix was passed to the function, remove it from | |
| # the end of the resulting string. | |
| dir=${dir%"$2"} | |
| # Print the resulting string and if it is empty, | |
| # print '/'. | |
| printf '%s\n' "${dir:-/}" | |
| } | |
| ### | |
| # Check if LocalWP process is running | |
| # @return 0 if running, 1 if not | |
| function localwp_is_process_running { | |
| case $(localwp_get_arch) in | |
| linux) | |
| # On Linux, check for the local process | |
| pgrep -x "local" >/dev/null 2>&1 && return 0 | |
| # Also try with full path matching | |
| pgrep -f "/opt/Local/local" >/dev/null 2>&1 && return 0 | |
| ;; | |
| darwin-arm64|darwin) | |
| # On macOS, check for Local.app process | |
| # Check both "Local" and "Local Helper" processes | |
| pgrep -f "Local.app/Contents/MacOS/Local" >/dev/null 2>&1 && return 0 | |
| # Fallback: check if Local.app is in running applications | |
| osascript -e 'tell application "System Events" to (name of processes) contains "Local"' 2>/dev/null | grep -q "true" && return 0 | |
| ;; | |
| esac | |
| return 1 | |
| } | |
| ### | |
| # Start LocalWP. | |
| # @param $1 Path to the detected LocalWP app. If missing, we just Hail Mary on Local.app | |
| function localwp_launch_localwp { | |
| # prevent broken shell from infinitely trying to start Local. | |
| [ ! -e "$LOCAL_CLI" ] && echo LOCAL_CLI error && return 0; | |
| # Check if local-cli is already responsive | |
| _localwp_safe_cli_call list-sites >/dev/null 2>&1 && return 0 | |
| # Check if the process is already running but not yet responsive | |
| if localwp_is_process_running; then | |
| echo -n "Local.app is starting up, waiting for it to be ready..." >&2 | |
| local TIMEOUT=30 | |
| local RETRY=$TIMEOUT | |
| # loop until we have a connection to the local-cli | |
| while [ $RETRY -gt 0 ]; do | |
| _localwp_safe_cli_call list-sites >/dev/null 2>&1 && break | |
| sleep 1 | |
| RETRY=$((RETRY-1)) | |
| echo -n "." >&2 | |
| done | |
| echo "" >&2 | |
| _localwp_safe_cli_call list-sites >/dev/null 2>&1 && \ | |
| echo "Local.app became ready in $((TIMEOUT-RETRY)) seconds." >&2 || \ | |
| echo "Local.app did not become ready in $TIMEOUT seconds." >&2 | |
| return 0 | |
| fi | |
| # Process is not running, launch it | |
| echo -n "Starting Local.app ... Please wait ..." >&2 | |
| # if linux, get local_app, start in background | |
| if [ "linux" = "$(localwp_get_arch)" ]; then | |
| $NOHUP "$LOCAL_APP_LOCATION" >/dev/null 2>&1 & | |
| else | |
| open -a "$LOCAL_APP_LOCATION" | |
| fi | |
| # start a retry counter at 15; we wait at most 15 seconds for the app to start. | |
| local TIMEOUT=15 | |
| local RETRY=$TIMEOUT | |
| # loop until we have a connection to the local-cli | |
| while [ $RETRY -gt 0 ]; do | |
| _localwp_safe_cli_call list-sites >/dev/null 2>&1 && break | |
| sleep 1 | |
| RETRY=$((RETRY-1)) | |
| # echo a dot, no new line | |
| echo -n "." >&2 | |
| done | |
| echo "" >&2 | |
| _localwp_safe_cli_call list-sites >/dev/null 2>&1 && \ | |
| echo "Local.app started in $((TIMEOUT-RETRY)) seconds." >&2 || \ | |
| echo "Local.app did not start in $TIMEOUT seconds." >&2 | |
| } | |
| ### | |
| # Find the MySQL binary path for a specific version, it will auto-detect the architecture and try the best match. | |
| # @param $1 MySQL version | |
| # | |
| function localwp_find_mysql_binary_path { | |
| LOCAL_SYSTEM_ARCHITECTURE=$(localwp_get_arch) | |
| setopt nullglob | |
| for i in \ | |
| ${LOCAL_RUNTIME_DIRECTORY}/lightning-services/mysql-${1}*/bin/${LOCAL_SYSTEM_ARCHITECTURE}/bin \ | |
| ${LOCAL_APP_RESOURCES}/extraResources/lightning-services/mysql-${1}*/bin/${LOCAL_SYSTEM_ARCHITECTURE}/bin \ | |
| ${LOCAL_RUNTIME_DIRECTORY}/lightning-services/mysql-${1}*/bin/darwin/bin \ | |
| ${LOCAL_APP_RESOURCES}/extraResources/lightning-services/mysql-${1}*/bin/darwin/bin \ | |
| ; do | |
| if [ -d "$i" ]; then | |
| export _LOCALWP_INTERNAL_CALL=1 | |
| pushd $i >/dev/null 2>&1 | |
| MYSQL_PATH=$(pwd -P) | |
| popd >/dev/null 2>&1 | |
| unset _LOCALWP_INTERNAL_CALL | |
| break; | |
| else | |
| MYSQL_PATH="" | |
| fi | |
| done | |
| unsetopt nullglob | |
| echo "$MYSQL_PATH" | |
| } | |
| ### | |
| # Find the PHP binary path for a specific version, it will auto-detect the architecture and try the best match. | |
| # @param $1 PHP version | |
| # | |
| function localwp_find_php_binary_path { | |
| LOCAL_SYSTEM_ARCHITECTURE=$(localwp_get_arch) | |
| setopt nullglob | |
| for i in \ | |
| ${LOCAL_RUNTIME_DIRECTORY}/lightning-services/php-${1}*/bin/${LOCAL_SYSTEM_ARCHITECTURE}/bin \ | |
| ${LOCAL_APP_RESOURCES}/extraResources/lightning-services/php-${1}*/bin/${LOCAL_SYSTEM_ARCHITECTURE}/bin \ | |
| ${LOCAL_RUNTIME_DIRECTORY}/lightning-services/php-${1}*/bin/darwin/bin \ | |
| ${LOCAL_APP_RESOURCES}/extraResources/lightning-services/php-${1}*/bin/darwin/bin \ | |
| ; do | |
| if [ -d "$i" ]; then | |
| export _LOCALWP_INTERNAL_CALL=1 | |
| pushd "$i" >/dev/null 2>&1 | |
| PHP_PATH=$(pwd -P) | |
| popd >/dev/null 2>&1 | |
| unset _LOCALWP_INTERNAL_CALL | |
| break; | |
| else | |
| PHP_PATH="" | |
| fi | |
| done | |
| unsetopt nullglob | |
| echo "$PHP_PATH" | |
| } | |
| ### | |
| # Find the Imagick module path for a specific PHP version, it will auto-detect the architecture and try the best match. | |
| # @param $1 PHP version | |
| # | |
| function localwp_find_php_imagick_path { | |
| LOCAL_SYSTEM_ARCHITECTURE=$(localwp_get_arch) | |
| setopt nullglob | |
| for i in \ | |
| "${LOCAL_RUNTIME_DIRECTORY}/lightning-services/php-${1}+0/bin/${LOCAL_SYSTEM_ARCHITECTURE}/ImageMagick/modules-Q16/coders" \ | |
| "${LOCAL_APP_RESOURCES}/extraResources/lightning-services/php-${1}*/bin/${LOCAL_SYSTEM_ARCHITECTURE}/ImageMagick/modules-Q16/coders \ | |
| "${LOCAL_RUNTIME_DIRECTORY}/lightning-services/php-${1}+0/bin/darwin/ImageMagick/modules-Q16/coders" \ | |
| "${LOCAL_APP_RESOURCES}/extraResources/lightning-services/php-${1}*/bin/darwin/ImageMagick/modules-Q16/coders \ | |
| ; do | |
| if [ -d "$i" ]; then | |
| export _LOCALWP_INTERNAL_CALL=1 | |
| pushd $i >/dev/null 2>&1 | |
| MGK_PATH=$(pwd -P) | |
| popd >/dev/null 2>&1 | |
| unset _LOCALWP_INTERNAL_CALL | |
| break; | |
| else | |
| MGK_PATH="" | |
| fi | |
| done | |
| unsetopt nullglob | |
| echo "$MGK_PATH" | |
| } | |
| ### | |
| # Get the nth word from a string | |
| # @param $1 Nth word | |
| # @param $2..$N Strings | |
| # | |
| function localwp_get_nth_word { | |
| local NTH="$1" | |
| shift; | |
| local WORDS=($*) | |
| local WORD="${WORDS[$NTH]}" | |
| echo "$WORD" | |
| } | |
| ### | |
| # Set the environment for a LocalWP site | |
| # @param $1 Site name | |
| # @param $2 Site ID | |
| # @param $3 MySQL version | |
| # @param $4 PHP version | |
| # | |
| function localwp_set_env { | |
| if [ "yes" = $AUTO_START_LOCALWP_APP ]; then | |
| localwp_launch_localwp | |
| fi | |
| echo -e "\033[33mSwitching to Local Shell for ${1}\033[0m" >&2 | |
| if [ -e "$LOCAL_CLI" ]; then | |
| SITE_STATE=$(localwp_get_nth_word 6 $(_localwp_safe_cli_call list-sites | grep -- "$2")) | |
| [ "halted" = "$SITE_STATE" ] && ( echo -e "\033[33m${1} not running, starting ...\033[0m" >&2 && _localwp_polling_start_site "$2" ) || echo -n '' >&2 | |
| fi | |
| export WP_ENV=local-by-flywheel | |
| export MYSQL_HOME="${LOCAL_RUNTIME_DIRECTORY}/run/${2}/conf/mysql" | |
| export PHPRC="${LOCAL_RUNTIME_DIRECTORY}/run/${2}/conf/php" | |
| export WP_CLI_CONFIG_PATH="$LOCAL_APP_RESOURCES/extraResources/bin/wp-cli/config.yaml" | |
| export WP_CLI_DISABLE_AUTO_CHECK_UPDATE=1 | |
| PATH="$(localwp_find_mysql_binary_path ${3}):$PATH" | |
| PATH="$(localwp_find_php_binary_path ${4}):$PATH" | |
| PATH="${LOCAL_APP_RESOURCES}/extraResources/bin/wp-cli/posix:$PATH" | |
| PATH="${LOCAL_APP_RESOURCES}/extraResources/bin/composer/posix:$PATH" | |
| export PATH | |
| MGK=$(localwp_find_php_imagick_path ${4}) | |
| [ "" != "${MGK}" ] && export MAGICK_CODER_MODULE_PATH="${MGK}" | |
| export LOCALWP_SITE="$1" | |
| export LOCALWP_SITE_ID="$2" | |
| [ "" = "$LD_LIBRARY_PATH" ] && LD_LIBRARY_PATH="$(localwp_find_php_binary_path ${4})"/../shared-libs | |
| export LD_LIBRARY_PATH | |
| local WEB_PATH="$(ls -d1 "${SITE_PATH}"/app/public "${SITE_PATH}"/app/public_html "${SITE_PATH}"/app/web 2>/dev/null | grep app | head -n1)" | |
| local COMPOSER_PATH="$(ls -d1 "${WEB_PATH}"/composer.json "${WEB_PATH}"/../composer.json "${WEB_PATH}"/../../composer.json 2>/dev/null | grep composer.json | head -n1)" | |
| if [ -f "$COMPOSER_PATH" ]; then | |
| local SITE_PHP_VERSION=$(jq -r .config.platform.php "$COMPOSER_PATH") | |
| if [ ! -z $SITE_PHP_VERSION ] && [ "null" != "$SITE_PHP_VERSION" ]; then | |
| ( echo "$SITE_PHP_VERSION" | php -r '$mm=function($in) { $in = explode(".", $in); return (float)"{$in[0]}.{$in[1]}"; }; $a = file_get_contents("php://stdin"); $a = $mm($a); $p = $mm(PHP_VERSION); exit (version_compare($a,$p));' ) || \ | |
| ( echo -e "\033[36mPHP Version in composer.json differs from LocalWP configured version. \033[0m" >&2 ; echo -e "\033[36mcomposer.json: $SITE_PHP_VERSION, LocalWP: $(php -r 'echo PHP_VERSION;'). \033[0m" >&2 ) | |
| fi | |
| fi | |
| } | |
| ### | |
| # Unset the environment for a LocalWP site | |
| # | |
| function localwp_unset_env { | |
| unset LD_LIBRARY_PATH | |
| unset WP_ENV | |
| unset MYSQL_HOME | |
| unset PHPRC | |
| unset WP_CLI_CONFIG_PATH | |
| unset WP_CLI_DISABLE_AUTO_CHECK_UPDATE | |
| unset MAGICK_CODER_MODULE_PATH | |
| # remove paths from PATH | |
| # remove paths matching ${LOCAL_APP_LOCATION}/Contents/Resources/extraResources/bin/ | |
| # remove paths matching ${LOCAL_RUNTIME_DIRECTORY}/lightning-services | |
| PATH=$(echo $PATH | tr ':' '\n' | grep -v "${LOCAL_APP_RESOURCES}/extraResources/" | grep -v "${LOCAL_RUNTIME_DIRECTORY}/lightning-services" | tr '\n' ':') | |
| # trim trailing/leading colons and remove multiple colons in a row | |
| PATH=$(echo $PATH | sed 's/:*$//;s/^:*//;s/:+/:/g') | |
| export PATH | |
| export LOCALWP_SITE= | |
| export LOCALWP_SITE_ID= | |
| } | |
| ### | |
| # MAIN Automatically switch to the LocalWP shell when entering a LocalWP site directory | |
| # | |
| function localwp_auto_switch { | |
| if [ "" != "$_LOCALWP_INTERNAL_CALL" ]; then | |
| return; | |
| fi | |
| [ "" = "$(which jq)" ] && return; | |
| SITES_JSON_PATH="$LOCAL_RUNTIME_DIRECTORY"/sites.json | |
| THEPWD="$(pwd)" | |
| if [[ "$THEPWD" =~ /.+/([^/]+)/app($|/.*) ]]; then | |
| ## variable cutting, on first match with %%, on last match with % . | |
| SITE_PATH=${THEPWD%%/app/*} | |
| SITE_ID=$(jq -r ".[] | select(.path == \"$SITE_PATH\") | .id" "$SITES_JSON_PATH") | |
| # does not seem to be a localwp site, but maybe the path is different | |
| if [ "" = "$SITE_ID" ]; then | |
| SITE_REAL_PATH=$(realpath "$SITE_PATH") | |
| SITE_ID=$(jq -r ".[] | select(.path == \"$SITE_REAL_PATH\") | .id" "$SITES_JSON_PATH") | |
| SITE_PATH="$SITE_REAL_PATH" | |
| fi | |
| # still no site id, we are done. | |
| [ "" = "$SITE_ID" ] && return; | |
| # PHP Config already pointing to this site, we assume all is o.k. | |
| # This is because this variable is set by the localwp_set_env function | |
| if [[ "$PHPRC" =~ "/$SITE_ID/" ]]; then | |
| return | |
| fi | |
| # get the site name | |
| SITE_NAME=$(_localwp_basename "$SITE_PATH") | |
| MYSQL_VERSION=$(jq -r ".[] | select(.path == \"$SITE_PATH\") | .services.mysql.version" "$SITES_JSON_PATH") | |
| PHP_VERSION=$(jq -r ".[] | select(.path == \"$SITE_PATH\") | .services.php.version" "$SITES_JSON_PATH") | |
| echo -n -e "\033]0;Local Shell for ${SITE_NAME}\007" | |
| # clear the environment | |
| localwp_unset_env | |
| # set the environment | |
| localwp_set_env $SITE_NAME $SITE_ID $MYSQL_VERSION $PHP_VERSION | |
| elif [[ "$PHPRC" =~ "/$SITE_ID/" ]]; then | |
| # clear the environment | |
| localwp_unset_env | |
| # notify the user | |
| echo -e "\033[33mSwitching off Local Shell\033[0m" >&2 | |
| # clear the title | |
| echo -n -e "\033]0;\007" | |
| fi | |
| } | |
| function localwp_siteid_from_name { | |
| SITES_JSON_PATH="$LOCAL_RUNTIME_DIRECTORY"/sites.json | |
| SITE_ID=$(jq -r ".[] | select(.name == \"$1\") | .id" "$SITES_JSON_PATH") | |
| if [ "" = "$SITE_ID" ]; then | |
| echo "Site not found: $1" | |
| return 1 | |
| fi | |
| echo "$SITE_ID" | |
| } | |
| function localwp_site_info_query { | |
| # $1 is the query, $2 is the site id | |
| SITES_JSON_PATH="$LOCAL_RUNTIME_DIRECTORY"/sites.json | |
| jq -r .\"${2:-$SITE_ID}\"${1} "$SITES_JSON_PATH" | |
| } | |
| function localwp_evaluatepath { | |
| local THE_PATH="$1" | |
| local OLD_PATH="$1" | |
| if [ -L "$THE_PATH" ] ; then | |
| THE_PATH=$(readlink -f "$THE_PATH") | |
| fi | |
| if [ -e "$THE_PATH" ]; then | |
| THE_PATH=$(realpath "$THE_PATH") | |
| fi | |
| if [ "$THE_PATH" != "$OLD_PATH" ]; then | |
| localwp_evaluatepath "$THE_PATH" | |
| else | |
| # no change, return the path | |
| echo "$THE_PATH" | |
| fi | |
| } | |
| function localwp_print_info { | |
| local S="${1:-$SITE_ID}" | |
| local SITE_PATH="$(localwp_site_info_query .path "${S}")" | |
| echo "SiteID: " ${S} | |
| local ITEM | |
| ITEM="${SITE_PATH}"/app | |
| echo "Site root from current path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM=$(ls -d1 "${SITE_PATH}"/app/public "${SITE_PATH}"/app/public_html "${SITE_PATH}"/app/web 2>/dev/null | grep app | head -n1) | |
| echo "Web root for this site (by disk): " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${SITE_PATH}"/app/.idea | |
| echo ".IDEA folder location (by disk): " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${LOCAL_RUNTIME_DIRECTORY}"/run/"${S}"/conf/apache/site.conf | |
| local APACHECONF="$ITEM" | |
| echo "Apache config path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${SITE_PATH}"/conf/apache/site.conf.hbs | |
| echo "Apache config template path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM=$(grep DocumentRoot "$APACHECONF" -m 1) | |
| # data is in the form: DocumentRoot "/Users/username/Local Sites/sitename/app/public" | |
| # Remove everything (" included) up til the first " found | |
| ITEM=${ITEM#*\"} | |
| # Remove everything (" included) after the first " found | |
| ITEM=${ITEM%\"*} | |
| echo "Web root for this ste (Apache): " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${LOCAL_RUNTIME_DIRECTORY}"/run/"${S}"/conf/php/php.ini | |
| local PHPCONF="$ITEM" | |
| echo "PHP config path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${SITE_PATH}"/conf/php/php.ini.hbs | |
| echo "PHP config template path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM=$(grep openssl.cafile "$PHPCONF" -m 1) | |
| # data is in the form: openssl.cafile="/Users/username/Local Sites/sitename/app/public/wp-includes/certificates/ca-bundle.crt" | |
| # Remove everything ( = included) up til the first " found | |
| ITEM=${ITEM#*\"} | |
| # Remove everything (" included) after the first " found | |
| ITEM=${ITEM%\"*} | |
| echo "WordPress SSL CA Cert path (PHP): " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| local NAME="$(localwp_site_info_query .name "${S}")" | |
| ITEM="${LOCAL_RUNTIME_DIRECTORY}/run/router/nginx/conf/route.${NAME}.test.conf" | |
| echo "Router Config: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="${LOCAL_RUNTIME_DIRECTORY}/run/router/nginx/certs/${NAME}.test.crt" | |
| echo "SSL Certificate: " $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| ITEM="$LOCAL_APP_LOCATION" | |
| echo "LocalWP Application path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $ITEM | |
| echo " =>" $(localwp_evaluatepath "$ITEM") | |
| ITEM="$LOCAL_RUNTIME_DIRECTORY" | |
| echo "LocalWP Runtime path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $ITEM | |
| echo " =>" $(localwp_evaluatepath "$ITEM") | |
| ITEM="$LOCAL_APP_RESOURCES" | |
| echo "LocalWP Resources path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $ITEM | |
| echo " =>" $(localwp_evaluatepath "$ITEM") | |
| ITEM="$LOCAL_RUNTIME_DIRECTORY"/sites.json | |
| echo "LocalWP Sites JSON path: " $( [ -e "$ITEM" ] && echo √ || echo x ) $ITEM | |
| echo " =>" $(localwp_evaluatepath "$ITEM") | |
| echo "TCP/IP ports:" | |
| echo " Apache: " $(localwp_site_info_query ".services.apache.ports.HTTP[0]" "${S}") | |
| echo " MySQL: " $(localwp_site_info_query ".services.mysql.ports.MYSQL[0]" "${S}") | |
| ITEM="$LOCAL_RUNTIME_DIRECTORY/run/${S}/mysql/mysqld.sock" | |
| echo " socket:" $( [ -e "$ITEM" ] && echo √ || echo x ) $(localwp_evaluatepath "$ITEM") | |
| echo " PHP CGI: " $(localwp_site_info_query ".services.php.ports.cgi[0]" "${S}") | |
| echo " MailPit UI: " $(localwp_site_info_query ".services.mailpit.ports.WEB[0]" "${S}") | |
| echo " MailPit SMTP: " $(localwp_site_info_query ".services.mailpit.ports.SMTP[0]" "${S}") | |
| echo "URLs:" | |
| echo " Website: " http://$(localwp_site_info_query ".domain" "${S}")/ | |
| echo " Mailpit: " http://$(localwp_site_info_query ".domain" "${S}"):$(localwp_site_info_query ".services.mailpit.ports.WEB[0]" "${S}")/ | |
| } | |
| function localwp { | |
| if [ "" = "$1" ]; then | |
| echo "Usage: localwp <command>" | |
| echo "Commands:" | |
| echo " start <site> Start a LocalWP site by name" | |
| echo " stop <site> Stop a LocalWP site by name" | |
| echo " restart <site> Restart a LocalWP site by name" | |
| echo " status <site> Get the status of a LocalWP site by name" | |
| echo " info Show relevant information on the current site" | |
| echo " list List all LocalWP sites" | |
| echo " refresh Refresh the current environment (e.g. when PHP version changed)" | |
| [ -n "$(which dblab)" ] && echo " dblab Open dblab for this site" | |
| [ -n "$(which lazysql)" ] && echo " lazysql Open lazysql for this site" | |
| echo " selfupdate Update this script from GIT" | |
| echo " selfreload Reload script in current session" | |
| echo " selfedit Edit this script with editor defined in \$EDITOR" | |
| return 1 | |
| fi | |
| if [ "yes" = $AUTO_START_LOCALWP_APP ]; then | |
| localwp_launch_localwp | |
| fi | |
| local CURSITE="$LOCALWP_SITE_ID" | |
| [ "$2" != "" ] && CURSITE=$(localwp_siteid_from_name "$2") | |
| case "$1" in | |
| start) | |
| _localwp_polling_start_site "$CURSITE" | |
| ;; | |
| stop) | |
| _localwp_safe_cli_call stop-site "$CURSITE" | |
| ;; | |
| restart) | |
| _localwp_safe_cli_call stop-site "$CURSITE" | |
| _localwp_polling_start_site "$CURSITE" | |
| ;; | |
| status) | |
| _localwp_safe_cli_call list-sites | grep -- "$CURSITE" | |
| ;; | |
| info) | |
| localwp_print_info "$CURSITE" | |
| ;; | |
| list) | |
| _localwp_safe_cli_call list-sites | |
| ;; | |
| selfreload) | |
| echo "Reloading localwp script..." | |
| source "$ZSH_AUTO_LOCALWP_PATH" | |
| ;; | |
| refresh) | |
| echo "Refreshing environment..." | |
| [ ! -z $LOCALWP_SITE_ID ] && localwp_unset_env | |
| localwp_auto_switch | |
| ;; | |
| dblab|db) | |
| if [ -z "$(which dblab)" ]; then | |
| echo "dblab is not installed or not in PATH" | |
| return 1 | |
| fi | |
| local SOCKET="$LOCAL_RUNTIME_DIRECTORY/run/${CURSITE}/mysql/mysqld.sock" | |
| if [ ! -e "$SOCKET" ]; then | |
| echo "MySQL socket not found at: $SOCKET" | |
| echo "Is the site running?" | |
| return 1 | |
| fi | |
| dblab --socket "$SOCKET" --db local --user root --pass root --driver mysql | |
| ;; | |
| lazysql) | |
| if [ -z "$(which lazysql)" ]; then | |
| echo "lazysql is not installed or not in PATH" | |
| return 1 | |
| fi | |
| local SOCKET="$LOCAL_RUNTIME_DIRECTORY/run/${CURSITE}/mysql/mysqld.sock" | |
| if [ ! -e "$SOCKET" ]; then | |
| echo "MySQL socket not found at: $SOCKET" | |
| echo "Is the site running?" | |
| return 1 | |
| fi | |
| lazysql "mysql://root:root@/$SOCKET/local" | |
| ;; | |
| selfupdate) | |
| echo "Updating localwp script..." | |
| cd "$(dirname "$ZSH_AUTO_LOCALWP_PATH")" || { echo "Failed to change directory to $(dirname "$ZSH_AUTO_LOCALWP_PATH")"; return 1; } | |
| # fetch the latest version from the gist | |
| [ -d .git ] && git pull || { echo "Not a git repository, cannot update."; return 1; } | |
| cd - >/dev/null || { echo "Failed to return to previous directory."; return 1; } | |
| echo "Update complete." | |
| ;; | |
| selfedit) | |
| if [ -z "$EDITOR" ]; then | |
| echo "No editor defined in \$EDITOR, please set it to your preferred editor." | |
| return 1 | |
| fi | |
| echo "$EDITOR" "$ZSH_AUTO_LOCALWP_PATH" >&2 | |
| "$EDITOR" "$ZSH_AUTO_LOCALWP_PATH" | |
| ;; | |
| *) | |
| echo "Unknown command: $1" | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| # autoload the hook | |
| autoload -U add-zsh-hook | |
| add-zsh-hook chpwd localwp_auto_switch |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment