Skip to content

Instantly share code, notes, and snippets.

@monotykamary
Last active June 30, 2025 13:04
Show Gist options
  • Save monotykamary/09ee6dc0031db66c63aa58cdcd214191 to your computer and use it in GitHub Desktop.
Save monotykamary/09ee6dc0031db66c63aa58cdcd214191 to your computer and use it in GitHub Desktop.
Calling devpod binaries as if they were on your machine
#!/usr/bin/env bash
# Requires direnv: https://direnv.net/
# Check if this is a devcontainer project
if [[ ! -f .devcontainer/devcontainer.json ]] && [[ ! -f devcontainer.json ]]; then
return # Not a devcontainer project, do nothing
fi
if ! command -v devpod >/dev/null 2>&1; then
return # devpod not available, do nothing
fi
log_status "Setting up devcontainer command wrappers..."
# Get project name for devpod
PROJECT_NAME=$(basename "$(pwd)")
# Environment variables to forward to devcontainer
ENV_VARS_TO_FORWARD=(
RAILS_ENV
NODE_ENV
RACK_ENV
APP_ENV
ENVIRONMENT
DEBUG
VERBOSE
VISUAL
EDITOR
PAGER
)
# Commands to wrap (will also auto-discover from bin/)
COMMANDS_TO_WRAP=(
ruby
yarn
bundle
rails
rake
rspec
rubocop
ruby-lsp
pnpm
make
)
# Auto-discover executables in bin/ folder (macOS compatible)
if [[ -d bin ]]; then
while IFS= read -r -d '' file; do
if [[ -x "$file" ]]; then
COMMANDS_TO_WRAP+=($(basename "$file"))
fi
done < <(find bin -maxdepth 1 -type f -perm +111 -print0 2>/dev/null)
fi
# Remove duplicates from commands array
readarray -t UNIQUE_COMMANDS < <(printf '%s\n' "${COMMANDS_TO_WRAP[@]}" | sort -u)
# Create wrapper functions and add to PATH
WRAPPER_DIR="$PWD/.direnv/bin"
rm -r "$WRAPPER_DIR"
mkdir -p "$WRAPPER_DIR"
for cmd in "${UNIQUE_COMMANDS[@]}"; do
# Skip if command name contains special characters
if [[ ! "$cmd" =~ ^[a-zA-Z0-9_-]+$ ]]; then
continue
fi
# Create wrapper script instead of function
cat >"$WRAPPER_DIR/$cmd" <<EOF
#!/usr/bin/env bash
set -euo pipefail
PROJECT_NAME="$PROJECT_NAME"
ENV_VARS_TO_FORWARD=(${ENV_VARS_TO_FORWARD[*]})
# Build environment variable exports
env_exports=""
for var in "\${ENV_VARS_TO_FORWARD[@]}"; do
if [[ -n "\${!var:-}" ]]; then
env_exports+="export \$var=\$(printf '%q' "\${!var}"); "
fi
done
# Determine which command to run
cmd_to_run=""
if [[ -x "$PWD/bin/$cmd" ]]; then
cmd_to_run="bin/$cmd"
else
cmd_to_run="$cmd"
fi
# Build the full command with arguments
full_command="\${env_exports}\${cmd_to_run}"
for arg in "\$@"; do
full_command+=" \$(printf '%q' "\$arg")"
done
# Execute in devcontainer
# Use interactive bash to ensure .bashrc is sourced
exec devpod ssh "\$PROJECT_NAME" \\
--silent \\
--workdir '/workspace' \\
--command "bash -i -c '\$full_command'"
EOF
# Make wrapper executable
chmod +x "$WRAPPER_DIR/$cmd"
done
# Add wrapper directory to PATH (this takes precedence)
PATH_add "$WRAPPER_DIR"
log_status "Devcontainer wrappers active for: ${UNIQUE_COMMANDS[*]}"
log_status "Commands will automatically execute in devcontainer"
setup_devcontainer_wrappers() {
local static_commands=("ruby" "node" "npm" "yarn" "python" "pip" "bundle" "rails" "rake" "rspec" "rubocop" "pnpm" "cargo" "go")
local bin_commands=()
# Add commands from bin/ folder if it exists
if [[ -d "bin" ]]; then
for file in bin/*; do
if [[ -x "$file" && -f "$file" ]]; then
local basename_file=$(basename "$file")
bin_commands+=("$basename_file")
fi
done
fi
# Combine static and dynamic commands, removing duplicates
local all_commands=($(printf '%s\n' "${static_commands[@]}" "${bin_commands[@]}" | sort -u))
for cmd in "${all_commands[@]}"; do
# Create wrapper function that captures common environment variables
eval "${cmd}() {
local project_name=\"\$(basename \"\$(pwd)\")\"
local current_cmd=\"${cmd}\"
# Capture key environment variables
local env_prefix=\"\"
for var in RAILS_ENV NODE_ENV RACK_ENV APP_ENV ENVIRONMENT DEBUG VERBOSE VISUAL EDITOR PAGER; do
if [[ -n \"\${ZSH_VERSION:-}\" ]]; then
local var_value=\${(P)var}
else
local var_value=\${!var}
fi
if [[ -n \"\$var_value\" ]]; then
env_prefix+=\"export \$var='\$var_value'; \"
fi
done
if [[ -f .devcontainer/devcontainer.json ]] || [[ -f devcontainer.json ]]; then
# Build command with escaped arguments
local full_cmd=\"\${env_prefix}\"
if [[ -x \"bin/\${current_cmd}\" ]]; then
full_cmd+=\"bin/\${current_cmd}\"
else
full_cmd+=\"\${current_cmd}\"
fi
for arg in \"\$@\"; do
full_cmd+=\" \$(printf '%q' \"\$arg\")\"
done
devpod ssh \"\$project_name\" --silent --workdir '/workspace' --command \"bash -l -c \$(printf '%q' \"\$full_cmd\")\"
else
if [[ -x \"bin/\${current_cmd}\" ]]; then
\"bin/\${current_cmd}\" \"\$@\"
else
command \${current_cmd} \"\$@\"
fi
fi
}"
done
}
# Simplified env wrapper for explicit env command usage
env() {
local env_vars=()
local cmd_and_args=()
local found_command=false
for arg in "$@"; do
if [[ ! $found_command && "$arg" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
env_vars+=("$arg")
else
found_command=true
cmd_and_args+=("$arg")
fi
done
if [[ ${#cmd_and_args[@]} -gt 0 ]]; then
local cmd="${cmd_and_args[0]}"
local args=("${cmd_and_args[@]:1}")
if declare -f "$cmd" >/dev/null 2>&1; then
local project_name="$(basename "$(pwd)")"
if [[ -f .devcontainer/devcontainer.json ]] || [[ -f devcontainer.json ]]; then
local env_exports=""
for env_var in "${env_vars[@]}"; do
env_exports+="export $(printf '%q' "$env_var"); "
done
local full_cmd="${env_exports}"
if [[ -x "bin/$cmd" ]]; then
full_cmd+="bin/$cmd"
else
full_cmd+="$cmd"
fi
for arg in "${args[@]}"; do
full_cmd+=" $(printf '%q' "$arg")"
done
devpod ssh "$project_name" --silent --workdir '/workspace' --command "bash -i -c $(printf '%q' "$full_cmd")"
else
if [[ -x "bin/$cmd" ]]; then
command env "${env_vars[@]}" "bin/$cmd" "${args[@]}"
else
command env "${env_vars[@]}" "$cmd" "${args[@]}"
fi
fi
else
command env "$@"
fi
else
command env "$@"
fi
}
# Hook to re-setup wrappers when changing directories
cd() {
builtin cd "$@"
setup_devcontainer_wrappers
}
pushd() {
builtin pushd "$@"
setup_devcontainer_wrappers
}
popd() {
builtin popd "$@"
setup_devcontainer_wrappers
}
# Initial setup
setup_devcontainer_wrappers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment