Preston Richey

Vibe coding a shell script

I recently converted a codebase from yarn to pnpm. Several times now I've ran yarn install only to see No lockfile found. and then I have to kill the process and run pnpm i. I was curious what it'd take to write a shell script to catch this sort of thing, so I turned to my good friend Claude.

Here's what we came up with, with support for npm, yarn, pnpm, bun, and deno:

#!/usr/bin/env bash
 
detect_package_manager() {
  if [ -f "pnpm-lock.yaml" ]; then
    echo "pnpm"
  elif [ -f "yarn.lock" ]; then
    echo "yarn"
  elif [ -f "package-lock.json" ] || [ -f "npm-shrinkwrap.json" ]; then
    echo "npm"
  elif [ -f "bun.lockb" ]; then
    echo "bun"
  elif [ -f "deno.lock" ] || [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then
    echo "deno"
  else
    echo ""
  fi
}
 
handle_package_manager() {
  local command_name="$1"
  shift
  local detected_package_manager
  local args=("$@")
  
  detected_package_manager=$(detect_package_manager)
  
  if [ -n "$detected_package_manager" ] && [ "$detected_package_manager" != "$command_name" ]; then
    echo "⚠️  Warning: This project uses $detected_package_manager, not $command_name."
    echo -n "Proceed? [yN] "
    read -r REPLY
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      echo "Command aborted. Try using $detected_package_manager instead."
      return 1
    fi
  fi
  
  command "$command_name" "${args[@]}"
  return $?
}
 
# Create wrapper functions for package managers
npm() {
  handle_package_manager "npm" "$@"
}
 
yarn() {
  handle_package_manager "yarn" "$@"
}
 
pnpm() {
  handle_package_manager "pnpm" "$@"
}
 
bun() {
  handle_package_manager "bun" "$@"
}
 
deno() {
  handle_package_manager "deno" "$@"
}

Here's what it looks like in practice:

➜ yarn
⚠️  Warning: This project uses pnpm, not yarn.
Proceed? [yN]
Command aborted. Try using pnpm instead.

I also came up with an installer script, which detects your shell environment (bash and zsh are supported, not fish) and installs the script to ~/.shell-scripts.

Click to download, or to copy and paste the code below.

#!/usr/bin/env bash
 
# Create a directory for your custom scripts if you don't have one
mkdir -p ~/.shell-scripts
 
# Create the package manager guard script
cat > ~/.shell-scripts/pm-guard.sh << 'EOF'
#!/usr/bin/env bash
 
# Function to detect project's package manager
detect_package_manager() {
  if [ -f "pnpm-lock.yaml" ]; then
    echo "pnpm"
  elif [ -f "yarn.lock" ]; then
    echo "yarn"
  elif [ -f "package-lock.json" ] || [ -f "npm-shrinkwrap.json" ]; then
    echo "npm"
  elif [ -f "bun.lockb" ]; then
    echo "bun"
  elif [ -f "deno.lock" ] || [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then
    echo "deno"
  else
    echo ""
  fi
}
 
# Helper function to handle package manager mismatch checks
# Arguments:
#   $1: package manager command name
#   $@: all original arguments
handle_package_manager() {
  local command_name="$1"
  shift
  local detected_package_manager
  local args=("$@")
  
  detected_package_manager=$(detect_package_manager)
  
  if [ -n "$detected_package_manager" ] && [ "$detected_package_manager" != "$command_name" ]; then
    echo "⚠️  Warning: This project uses $detected_package_manager, not $command_name."
    echo -n "Proceed? [yN] "
    read REPLY
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      echo "Command aborted. Try using $detected_package_manager instead."
      return 1
    fi
  fi
  
  command "$command_name" "${args[@]}"
  return $?
}
 
# Create wrapper functions for package managers
npm() {
  handle_package_manager "npm" "$@"
}
 
yarn() {
  handle_package_manager "yarn" "$@"
}
 
pnpm() {
  handle_package_manager "pnpm" "$@"
}
 
bun() {
  handle_package_manager "bun" "$@"
}
 
deno() {
  handle_package_manager "deno" "$@"
}
EOF
 
# Make the script executable
chmod +x ~/.shell-scripts/pm-guard.sh
 
# Function to add source line to shell config
add_to_shell_config() {
  local config_file="$1"
  local source_line="source ~/.shell-scripts/pm-guard.sh"
  
  # Add the line with a comment
  echo "" >> "$config_file"
  echo "# Source package manager guard" >> "$config_file"
  echo "$source_line" >> "$config_file"
  echo "Added PM Guard to $config_file"
  return 0
}
 
# Determine which shell config file to use
if [ -f "$HOME/.zshrc" ]; then
  add_to_shell_config "$HOME/.zshrc"
elif [ -f "$HOME/.bashrc" ]; then
  add_to_shell_config "$HOME/.bashrc"
elif [ -f "$HOME/.bash_profile" ]; then
  add_to_shell_config "$HOME/.bash_profile"
else
  echo "Could not find shell config file. Please add the following line to your shell config:"
  echo "source ~/.shell-scripts/pm-guard.sh"
  exit 1
fi
 
echo "Setup complete! Reload your shell or run 'source ~/.shell-scripts/pm-guard.sh' to activate."
echo ""
echo "Usage:"
echo "  - Normal usage: npm, yarn, pnpm, bun, deno (will check for mismatches)"

I'm blown away by how quickly I can go from idea to production script with LLM tools like Claude and V0. I was able to explore the solution space, suss out the scope, and iterate on implementation all in a few cycles of prompting.