To setup custom scripts to be available only when you are within any subdirectory of your project, do the following:
- install
direnvandgit - add
.envrcfile to the root directory of your project with this content:
export PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
PATH_add "$PROJECT_ROOT/.env/scripts"
- run
direnv allowin the root directory of your project - create folder
.env/scriptsand add any scripts you want available there – make them executable
Already now, if you have .env/scripts/test by typing test in any subdirectory of the project the test script should run.
Scoping the scripts into a custom command
If you don’t want the scripts exposed directly, not to pollute your PATH, do the following instead:
- install
direnvandgit - add
.envrcfile to the root directory of your project with this content:
export PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
- run
direnv allowin the root directory of your project - create
~/bin/folder - add
export PATH="~/bin/:$PATH"to the end of your.bashrc - create an executable file
~/bin/cmdwith the following content
#!/usr/bin/env bash
set -eo pipefail
SCRIPT_FOLDER="$PROJECT_ROOT/.env/scripts/"
show-help(){
printf "usage: $(basename "$0") <command> [<args>]\n"
items=()
if [[ -d "$SCRIPT_FOLDER" ]]; then
cd "$SCRIPT_FOLDER" || exit
script_list="$(find . -type f | sed 's|^\./||')"
while IFS='' read -r line; do
items+=("$line")
done <<< "$script_list"
printf " %s\n" "${items[@]}"
else
printf "\nfirst change directory to a project"
fi
}
command="$1"
case "$command" in
"" | "-h" | "--help" | "help")
show-help
;;
*)
shift
if [[ -f "$SCRIPT_FOLDER/$command" ]]; then
"$SCRIPT_FOLDER/$command" "$@"
else
echo "command not found: $command"
exit 1
fi
;;
esac
After your source .bashrc, when you run cmd from subfolder of your project the command prints whatever scripts you have in .env/scripts.
And, for example, if you run cmd test [<pars>] then the .env/test [<pars>] is invoked.
A more detailed description
The setup and the description below is meant for Linux machines.
Note that .envrc and .env are by hidden and will not be shown by default (use ls -a or enable hidden files in your file explorer).
direnv
direnv is a utility that changes your environment based on the subdirectory where you are.
You will find it within your package manager (apt, pacman, etc.).
We use this to detect the root folder of the project.
This utility detects whether a folder (or any ancestor folder) contains .envrc file, and executes it if so – however, to prevent unwanted invocation the user needs to allow every particular .envrc with direnv allow
Whenever the .envrc file changes direnv allow needs to be re-run.
The commands that should be used within the .env script are meant NOT to be permanent.
If you leave your project, whatever the script changed should be reverted.
For this to work you need to use a set of revertible commands that direnv defines, e.g. PATH_add, see its documentation ↗.
Custom scripts
Create your project scripts within .env/scripts/, you may rename this to anything.
I chose .env as it makes sense with .envrc setup.
Note that:
- to make your scripts executable run
chmod u+x <script> - omit extensions like
.shas those will be the necessary part of the command - setup command that runs your script using shebang:
#!/usr/bin/env bashon the first line of the script
Note that invoking a script does not change your folder – the script is run from your current directory.
You may use cd "$PROJECT_ROOT" for script to jump to the root of the project.
Alternately, if you want the scripts to be usable by users that do not have direnv then use cd "$(dirname "$0")/../.." || exit instead.
cmd
The instructions simply add the new script to be always available.
Its functionality is based on the PROJECT_ROOT variable that that is set up by direnv.
It would be nice to improve cmd help so that it prints descriptions of each command – which for now, I keep as comment on the second line of the file.
References
This setup is inspired by Upgrade your scripts using ‘direnv’ and ‘run’ script ↗ post by Oliver Nguyen. His setup extracts commands from functions of a single shell script. I wanted this simple functionality for too many years, that post is quite long, but will give you a list of reasons to set this up.
All the code in this post is dedicated to the public domain, use it without any restrictions but without any warranty.