mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2026-08-07 06:08:39 +02:00
feat(plugin): Add project-aliases plugin
This commit is contained in:
parent
677a4592b1
commit
9766bc570c
2 changed files with 385 additions and 0 deletions
127
plugins/project-aliases/README.md
Normal file
127
plugins/project-aliases/README.md
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
|
||||||
|
# Project Aliases (Zsh Plugin)
|
||||||
|
|
||||||
|
A lightweight **Zsh plugin** to manage **per-project shell aliases**.
|
||||||
|
Aliases are **loaded automatically** when you enter a project folder and **removed automatically** when you leave.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Quick Start
|
||||||
|
```bash
|
||||||
|
# Inside your project folder
|
||||||
|
echo "alias run='npm run dev'" > .proj_aliases
|
||||||
|
echo "alias test='npm test'" >> .proj_aliases
|
||||||
|
|
||||||
|
cd ~/my-project
|
||||||
|
# [project-aliases] Warning: Untrusted .proj_aliases found. Run 'palias allow' to trust and load it.
|
||||||
|
|
||||||
|
palias allow
|
||||||
|
# [project-aliases] Loaded and trusted aliases for project: /Users/username/my-project
|
||||||
|
|
||||||
|
run
|
||||||
|
# Executes: npm run dev
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
# [project-aliases] Project aliases removed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
- Define aliases per project in a `.proj_aliases` file.
|
||||||
|
- **🔒 Security-First**: Requires explicit trust (`palias allow`) before loading a `.proj_aliases` file. If the file is modified externally, the trust is invalidated, preventing silent execution of untrusted code.
|
||||||
|
- Automatically **load aliases** when entering a trusted project folder.
|
||||||
|
- Automatically **unload them** when leaving.
|
||||||
|
- Keep a clean shell environment — no alias pollution between projects.
|
||||||
|
- Helper commands:
|
||||||
|
- `palias list` → view currently active project aliases
|
||||||
|
- `palias edit` → edit currently project aliases
|
||||||
|
- `palias reload` → reload aliases for the current project without leaving the folder
|
||||||
|
- `palias allow` → trust and load aliases for the current project
|
||||||
|
- `palias deny` → untrust and unload aliases for the current project
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
### **Oh My Zsh**
|
||||||
|
1. Clone the plugin into your Oh My Zsh custom plugins folder:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/dvigo/project-aliases.git ~/.oh-my-zsh/custom/plugins/project-aliases
|
||||||
|
```
|
||||||
|
2. Enable it in your `~/.zshrc`:
|
||||||
|
```bash
|
||||||
|
plugins=(... project-aliases)
|
||||||
|
```
|
||||||
|
3. Reload Zsh:
|
||||||
|
```bash
|
||||||
|
source ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 Demo
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
No extra configuration is needed.
|
||||||
|
Simply create a `.proj_aliases` file in the root of any project you want to use aliases in.
|
||||||
|
|
||||||
|
**Example `.proj_aliases`**
|
||||||
|
```bash
|
||||||
|
alias run="npm run dev"
|
||||||
|
alias lint="eslint src/"
|
||||||
|
alias up="docker-compose up -d"
|
||||||
|
alias down="docker-compose down"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖊️ Usage
|
||||||
|
|
||||||
|
### Available commands:
|
||||||
|
```bash
|
||||||
|
palias list # Shows active project aliases
|
||||||
|
palias edit # Opens the current project's .proj_aliases in your editor
|
||||||
|
palias reload # Reload aliases from the current project's .proj_aliases
|
||||||
|
palias allow # Trust and load the current project's .proj_aliases
|
||||||
|
palias deny # Untrust and unload the current project's .proj_aliases
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example session:
|
||||||
|
```bash
|
||||||
|
cd ~/dev/my-api
|
||||||
|
# [project-aliases] Warning: Untrusted .proj_aliases found. Run 'palias allow' to trust and load it.
|
||||||
|
|
||||||
|
palias allow
|
||||||
|
# [project-aliases] Loaded and trusted aliases for project: /Users/username/dev/my-api
|
||||||
|
|
||||||
|
up # runs docker-compose up -d
|
||||||
|
down # runs docker-compose down
|
||||||
|
|
||||||
|
# If you modify .proj_aliases, it becomes untrusted again. Re-run allow to load it:
|
||||||
|
palias allow
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
# [project-aliases] Project aliases removed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Roadmap
|
||||||
|
- [ ] Add support for `.proj_aliases.d/` folder with multiple alias files.
|
||||||
|
- [ ] Add an option to persist aliases across shells until manually cleared.
|
||||||
|
- [ ] Add support for project-specific environment variables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 License
|
||||||
|
GNU General Public License v3.0 — See [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
|
---
|
||||||
258
plugins/project-aliases/project-aliases.plugin.zsh
Normal file
258
plugins/project-aliases/project-aliases.plugin.zsh
Normal file
|
|
@ -0,0 +1,258 @@
|
||||||
|
# project-aliases - Zsh plugin to auto-load and clean project-specific aliases from .proj_aliases, keeping your shell organized and project-ready.
|
||||||
|
# Copyright (C) 2025 David Vigo
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# Project Aliases Plugin
|
||||||
|
# ===========================
|
||||||
|
# Loads aliases defined in .proj_aliases of each project when entering its folder.
|
||||||
|
# Cleans previous aliases when leaving the project.
|
||||||
|
# ===========================
|
||||||
|
|
||||||
|
CURRENT_PROJECT=""
|
||||||
|
PROJECT_ALIASES_LIST=()
|
||||||
|
TRUST_DB="${XDG_CONFIG_HOME:-$HOME/.config}/project-aliases/trusted"
|
||||||
|
|
||||||
|
# Find project root (contains .proj_aliases or .git)
|
||||||
|
function find_project_root() {
|
||||||
|
local dir="$1"
|
||||||
|
while [[ "$dir" != "/" ]]; do
|
||||||
|
if [[ -f "$dir/.proj_aliases" || -d "$dir/.git" ]]; then
|
||||||
|
echo "$dir"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
dir=$(dirname "$dir")
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove aliases from previous project
|
||||||
|
function unset_project_aliases() {
|
||||||
|
for alias_name in "${PROJECT_ALIASES_LIST[@]}"; do
|
||||||
|
unalias "$alias_name" 2>/dev/null
|
||||||
|
done
|
||||||
|
PROJECT_ALIASES_LIST=()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate hash of a file portably
|
||||||
|
function _project_aliases_hash() {
|
||||||
|
local file="$1"
|
||||||
|
if (( $+commands[shasum] )); then
|
||||||
|
shasum -a 256 "$file" 2>/dev/null | awk '{print $1}'
|
||||||
|
elif (( $+commands[sha256sum] )); then
|
||||||
|
sha256sum "$file" 2>/dev/null | awk '{print $1}'
|
||||||
|
elif (( $+commands[md5sum] )); then
|
||||||
|
md5sum "$file" 2>/dev/null | awk '{print $1}'
|
||||||
|
elif (( $+commands[md5] )); then
|
||||||
|
md5 -q "$file" 2>/dev/null
|
||||||
|
else
|
||||||
|
# Safe fallback if no hashing command exists (uses file modification time and size)
|
||||||
|
local stats
|
||||||
|
stats=$(stat -f "%m%z" "$file" 2>/dev/null || stat -c "%Y%s" "$file" 2>/dev/null || echo "00")
|
||||||
|
echo "legacy-$stats"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if a .proj_aliases file is trusted
|
||||||
|
function check_project_aliases_trusted() {
|
||||||
|
local file="$1"
|
||||||
|
local abs_path
|
||||||
|
abs_path=$(realpath "$file" 2>/dev/null || readlink -f "$file" 2>/dev/null || echo "$file")
|
||||||
|
|
||||||
|
if [[ ! -f "$TRUST_DB" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current_hash
|
||||||
|
current_hash=$(_project_aliases_hash "$file")
|
||||||
|
|
||||||
|
local line
|
||||||
|
local found=1
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
local entry_hash="${line%% *}"
|
||||||
|
local entry_path="${line#* }"
|
||||||
|
# Strip leading/trailing space
|
||||||
|
entry_path="${entry_path#${entry_path%%[![:space:]]*}}"
|
||||||
|
entry_path="${entry_path%${entry_path##*[![:space:]]}}"
|
||||||
|
|
||||||
|
if [[ "$entry_hash" == "$current_hash" && "$entry_path" == "$abs_path" ]]; then
|
||||||
|
found=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < "$TRUST_DB"
|
||||||
|
return $found
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add current .proj_aliases to trusted database
|
||||||
|
function allow_project_aliases() {
|
||||||
|
local file="$CURRENT_PROJECT/.proj_aliases"
|
||||||
|
local abs_path
|
||||||
|
abs_path=$(realpath "$file" 2>/dev/null || readlink -f "$file" 2>/dev/null || echo "$file")
|
||||||
|
|
||||||
|
local current_hash
|
||||||
|
current_hash=$(_project_aliases_hash "$file")
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$TRUST_DB")"
|
||||||
|
|
||||||
|
if [[ -f "$TRUST_DB" ]]; then
|
||||||
|
local temp_db="${TRUST_DB}.tmp"
|
||||||
|
local line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
local entry_path="${line#* }"
|
||||||
|
entry_path="${entry_path#${entry_path%%[![:space:]]*}}"
|
||||||
|
entry_path="${entry_path%${entry_path##*[![:space:]]}}"
|
||||||
|
if [[ "$entry_path" != "$abs_path" ]]; then
|
||||||
|
echo "$line" >> "$temp_db"
|
||||||
|
fi
|
||||||
|
done < "$TRUST_DB"
|
||||||
|
mv "$temp_db" "$TRUST_DB" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${current_hash} ${abs_path}" >> "$TRUST_DB"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove current .proj_aliases from trusted database
|
||||||
|
function deny_project_aliases() {
|
||||||
|
local file="$CURRENT_PROJECT/.proj_aliases"
|
||||||
|
local abs_path
|
||||||
|
abs_path=$(realpath "$file" 2>/dev/null || readlink -f "$file" 2>/dev/null || echo "$file")
|
||||||
|
|
||||||
|
if [[ -f "$TRUST_DB" ]]; then
|
||||||
|
local temp_db="${TRUST_DB}.tmp"
|
||||||
|
local line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
local entry_path="${line#* }"
|
||||||
|
entry_path="${entry_path#${entry_path%%[![:space:]]*}}"
|
||||||
|
entry_path="${entry_path%${entry_path##*[![:space:]]}}"
|
||||||
|
if [[ "$entry_path" != "$abs_path" ]]; then
|
||||||
|
echo "$line" >> "$temp_db"
|
||||||
|
fi
|
||||||
|
done < "$TRUST_DB"
|
||||||
|
mv "$temp_db" "$TRUST_DB" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load aliases from a file and store names
|
||||||
|
function load_project_aliases_file() {
|
||||||
|
local file="$1"
|
||||||
|
# Extract alias names from the file
|
||||||
|
local names=($(grep -oE '^alias[[:space:]]+[a-zA-Z0-9_-]+' "$file" | awk '{print $2}'))
|
||||||
|
|
||||||
|
# Source the file to define the aliases
|
||||||
|
source "$file"
|
||||||
|
|
||||||
|
# Store names in PROJECT_ALIASES_LIST
|
||||||
|
for name in "${names[@]}"; do
|
||||||
|
PROJECT_ALIASES_LIST+=("$name")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load aliases for the current project
|
||||||
|
function load_project_aliases() {
|
||||||
|
local dir=$(pwd)
|
||||||
|
local root=$(find_project_root "$dir")
|
||||||
|
|
||||||
|
# Not in any project -> clean aliases
|
||||||
|
if [[ -z "$root" ]]; then
|
||||||
|
if [[ -n "$CURRENT_PROJECT" ]]; then
|
||||||
|
unset_project_aliases
|
||||||
|
CURRENT_PROJECT=""
|
||||||
|
echo "[project-aliases] Project aliases removed."
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Changed project -> clean and reload
|
||||||
|
if [[ "$root" != "$CURRENT_PROJECT" ]]; then
|
||||||
|
unset_project_aliases
|
||||||
|
CURRENT_PROJECT="$root"
|
||||||
|
if [[ -f "$root/.proj_aliases" ]]; then
|
||||||
|
if check_project_aliases_trusted "$root/.proj_aliases"; then
|
||||||
|
load_project_aliases_file "$root/.proj_aliases"
|
||||||
|
echo "[project-aliases] Aliases loaded from $root/.proj_aliases"
|
||||||
|
else
|
||||||
|
echo "[project-aliases] Warning: Untrusted .proj_aliases found. Run 'palias allow' to trust and load it."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command to list, edit, reload, allow, or deny aliases
|
||||||
|
function palias() {
|
||||||
|
case "$1" in
|
||||||
|
list)
|
||||||
|
if [[ ${#PROJECT_ALIASES_LIST[@]} -eq 0 ]]; then
|
||||||
|
echo "No active project aliases."
|
||||||
|
else
|
||||||
|
echo "Active project aliases:"
|
||||||
|
for name in "${PROJECT_ALIASES_LIST[@]}"; do
|
||||||
|
alias "$name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
edit)
|
||||||
|
if [[ -n "$CURRENT_PROJECT" && -f "$CURRENT_PROJECT/.proj_aliases" ]]; then
|
||||||
|
${EDITOR:-nano} "$CURRENT_PROJECT/.proj_aliases"
|
||||||
|
else
|
||||||
|
echo "No .proj_aliases file found in the current project."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
reload)
|
||||||
|
if [[ -n "$CURRENT_PROJECT" && -f "$CURRENT_PROJECT/.proj_aliases" ]]; then
|
||||||
|
if check_project_aliases_trusted "$CURRENT_PROJECT/.proj_aliases"; then
|
||||||
|
unset_project_aliases
|
||||||
|
load_project_aliases_file "$CURRENT_PROJECT/.proj_aliases"
|
||||||
|
echo "[project-aliases] Aliases reloaded from $CURRENT_PROJECT/.proj_aliases"
|
||||||
|
else
|
||||||
|
echo "[project-aliases] Warning: .proj_aliases is not trusted. Run 'palias allow' to trust and load it."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[project-aliases] No active project or missing .proj_aliases file."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
allow)
|
||||||
|
if [[ -n "$CURRENT_PROJECT" && -f "$CURRENT_PROJECT/.proj_aliases" ]]; then
|
||||||
|
allow_project_aliases
|
||||||
|
unset_project_aliases
|
||||||
|
load_project_aliases_file "$CURRENT_PROJECT/.proj_aliases"
|
||||||
|
echo "[project-aliases] Loaded and trusted aliases for project: $CURRENT_PROJECT"
|
||||||
|
else
|
||||||
|
echo "[project-aliases] No active project or missing .proj_aliases file."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
deny|disallow)
|
||||||
|
if [[ -n "$CURRENT_PROJECT" && -f "$CURRENT_PROJECT/.proj_aliases" ]]; then
|
||||||
|
deny_project_aliases
|
||||||
|
unset_project_aliases
|
||||||
|
echo "[project-aliases] Untrusted and unloaded aliases for project: $CURRENT_PROJECT"
|
||||||
|
else
|
||||||
|
echo "[project-aliases] No active project or missing .proj_aliases file."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: palias [list|edit|reload|allow|deny]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hook when changing directory
|
||||||
|
autoload -U add-zsh-hook
|
||||||
|
add-zsh-hook chpwd load_project_aliases
|
||||||
|
|
||||||
|
# Initial load
|
||||||
|
load_project_aliases
|
||||||
Loading…
Reference in a new issue