-
-
Save iwan-uschka/09445b4d94b45458cd43e266983a6f1f to your computer and use it in GitHub Desktop.
dev command with auto-completion
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
#compdef dev | |
function _dev() { | |
# set auto completion result | |
_describe 'command' __DEV_AUTOCOMPLETION_LIST | |
} |
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
# Originally created by Stefan Judis. See | |
# https://www.stefanjudis.com/snippets/a-custom-dev-command-with-auto-completion/ | |
# | |
# Creates a `dev` command for the shell (zsh) including autocompletion to | |
# provide a UI for selecting a dev project in (aka git repository cloned into) | |
# `$__DEV_ROOT_DIR_PATH`. | |
# | |
# Unlike to the solution of Stefan Judis, here the folder structure can be | |
# arbitrarily complex. A project directory just needs to be a valid git | |
# repository (containing a `.git` folder) to be identified by this script. | |
# | |
# Directories can be excluded explicitely which means they won't be scanned at | |
# all. | |
# | |
# Subdirectories of an identified project directory won't be scanned by default | |
# unless the project directory has been added to `$__DEV_FORCE_DIR_PATHS`. This | |
# can be helpful when using git submodules or workspaces. Example: | |
# `test-project-workspace` contains two submodules `test-sub-1` and | |
# `test-sub-2`. `test-project-workspace` needs to be added to | |
# `$__DEV_FORCE_DIR_PATHS` to enable searching for `.git` in its subdirectories. | |
# | |
# The final list containing all projects is stored into an index file | |
# physically. If the file doesn't exists it will be created automatically on | |
# shell startup. If the list needs to be created manually (after a new project | |
# has been added/created) `dev_create_index` needs to be called in the shell. | |
# | |
# Creating the index can take a while depending on the project structure. | |
# | |
# After selecting a project batch actions are executed for this project: | |
# - change directory in the shell | |
# - set node version via `nvm | |
# - run npm script `dev` if present | |
# | |
# Optional batch actions can be triggered by setting specific arguments: | |
# - `--code` / `-c` => open project in VS code | |
# - `--finder` / `-f` => open project in Finder | |
# - `--git` / `-g` => open project in Tower (git GUI) | |
# | |
# ZSH autocompletion needs to be installed and activated (see | |
# https://github.com/zsh-users/zsh-completions#using-zsh-frameworks). | |
# Final list to create. | |
__DEV_AUTOCOMPLETION_LIST=() | |
# Directory to scan for projects recursively. | |
__DEV_ROOT_DIR_PATH=~/projects | |
# Path to index cache file. | |
__DEV_INDEX_FILE_PATH="$ZSH/cache/dev-index" | |
# List of directory paths to prevent scanning for. | |
__DEV_EXCLUDE_DIR_PATHS=("/_Ressources" "/_github" "/_services" "*/platform-backups" "*/node_modules" "*/bower_components" "*/.*" "*/_*") | |
# List of directory paths to ignore when creating the autocompletion list. | |
__DEV_IGNORE_DIR_PATHS=("*/*-workspace") | |
# List of directory paths where subdirectory scanning should be forced even if | |
# the directory itself is a valid git repository. | |
__DEV_FORCE_DIR_PATHS=("*/*-workspace") | |
# Remove the following string at the end of each autocompletion list item. | |
# Example: `path/to/project/sub-dir-containing-project` should be displayed | |
# in autocompletion as `path/to/project`. In this case | |
# `__DEV_RTRIM_AUTOCOMPLETION="/sub-dir-containing-project"` does the trick. | |
__DEV_RTRIM_AUTOCOMPLETION="/production" | |
# Main shell command `dev`. | |
function dev() { | |
echo -e "Starting up \033[1m$1\033[0m ..." | |
PROJECT_DIR=$__DEV_ROOT_DIR_PATH/$1 | |
if [ -n "$__DEV_RTRIM_AUTOCOMPLETION" ]; then | |
if [ -d "$PROJECT_DIR$__DEV_RTRIM_AUTOCOMPLETION" ]; then | |
PROJECT_DIR+=$__DEV_RTRIM_AUTOCOMPLETION | |
fi | |
fi | |
while [[ "$#" -gt 0 ]]; do | |
case $1 in | |
-g | --git) OPEN_GIT_GUI=true ;; | |
-f | --finder) OPEN_FINDER=true ;; | |
-c | --code) OPEN_CODE_EDITOR=true ;; | |
*) | |
# echo "Unknown parameter passed: $1" | |
# exit 1 | |
;; | |
esac | |
shift | |
done | |
cd $PROJECT_DIR || return | |
if [ "$OPEN_GIT_GUI" = true ]; then | |
gittower . | |
fi | |
if [ "$OPEN_FINDER" = true ]; then | |
open . | |
fi | |
if [ "$OPEN_CODE_EDITOR" = true ]; then | |
code . | |
fi | |
if [ -f "$PROJECT_DIR/package.json" ]; then | |
NODE_VERSION=$(jq ".engines.node" <"$PROJECT_DIR/package.json") | |
DEV_COMMAND=$(jq ".scripts.dev" <"$PROJECT_DIR/package.json") | |
if [ "$NODE_VERSION" != "null" ]; then | |
# `nvm` doesn't provide an option to use a node version advised in | |
# package.json because it's advisory only and not proscriptive. See | |
# https://github.com/nvm-sh/nvm/issues/651 | |
# | |
# But it's always safe to choose the oldest version extractable from a | |
# semver range. The following examples work if `v12.20.0` has been | |
# installed via `nvm`: | |
# - `>=12.20.0` => `nvm use 12.20.0` => `v12.20.0` | |
# - `>=12` => `nvm use 12` => `v12.20.0` | |
# | |
# What doesn't work here are negative basic comparisons like | |
# - `!=` | |
# - '<' | |
nvm use $(sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p' <<<$NODE_VERSION) | |
fi | |
if [ "$DEV_COMMAND" != "null" ]; then | |
npm run dev | |
fi | |
fi | |
} | |
# Create index cache. | |
function dev_create_index() { | |
echo "Creating $__DEV_INDEX_FILE_PATH ..." | |
FIND_EXCLUDE_ARGS=() | |
for __DEV_EXCLUDE_DIR_PATH in "${__DEV_EXCLUDE_DIR_PATHS[@]}"; do | |
FIND_EXCLUDE_ARGS+=('-o' '-path' "$__DEV_ROOT_DIR_PATH$__DEV_EXCLUDE_DIR_PATH") | |
done | |
FIND_IGNORE_ARGS=() | |
for __DEV_IGNORE_DIR_PATH in "${__DEV_IGNORE_DIR_PATHS[@]}"; do | |
FIND_IGNORE_ARGS+=('-not' '-path' "$__DEV_ROOT_DIR_PATH$__DEV_IGNORE_DIR_PATH") | |
done | |
# `-name "$__DEV_ROOT_DIR_PATH"` will never be true so it's safe to add it | |
# here. This way `$_FIND_FORCE_ARGS` has at least one element even if | |
# `$__DEV_IGNORE_DIR_PATHS` is empty. This way an error like `empty | |
# expression` can be prevented later while subtituting | |
# `"${FIND_FORCE_ARGS[@]}"` in `find` statement. | |
FIND_FORCE_ARGS=('-name' "$__DEV_ROOT_DIR_PATH") | |
# first `-o` is removed later on because the array acts as an isolated expression | |
for __DEV_FORCE_DIR_PATH in "${__DEV_FORCE_DIR_PATHS[@]}"; do | |
FIND_FORCE_ARGS+=('-o' '-name' "$__DEV_ROOT_DIR_PATH$__DEV_FORCE_DIR_PATH") | |
done | |
find $__DEV_ROOT_DIR_PATH \ | |
-type d \ | |
"${FIND_IGNORE_ARGS[@]}" \ | |
\( \ | |
\( -exec sh -c 'test -d "$1"/.git' -- {} \; -print ! \( "${FIND_FORCE_ARGS[@]}" \) \) \ | |
"${FIND_EXCLUDE_ARGS[@]}" \ | |
\) -prune | | |
sort -u -f >$__DEV_INDEX_FILE_PATH | |
dev_set_autocompletion | |
} | |
function dev_set_autocompletion() { | |
while IFS= read -r line; do | |
__DEV_AUTOCOMPLETION_LIST+=$(sed -r "s|^$__DEV_ROOT_DIR_PATH/||;s|$__DEV_RTRIM_AUTOCOMPLETION$||" <<<$line) | |
done <$__DEV_INDEX_FILE_PATH | |
} | |
# Create index cache file if it doesn't exist on shell start up. | |
if [ ! -f "$__DEV_INDEX_FILE_PATH" ]; then | |
echo "$__DEV_INDEX_FILE_PATH does not exist." | |
dev_create_index | |
else | |
dev_set_autocompletion | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment