Skip to content

Instantly share code, notes, and snippets.

@subframe7536
Last active February 15, 2026 01:37
Show Gist options
  • Select an option

  • Save subframe7536/fb13f22a0796e477fd041e20200040f7 to your computer and use it in GitHub Desktop.

Select an option

Save subframe7536/fb13f22a0796e477fd041e20200040f7 to your computer and use it in GitHub Desktop.
A subset of `@antfu/ni` in pure shell script
#!/usr/bin/env sh
_ni_err() {
printf '%s\n' "$*" >&2
}
_ni_quote_arg() {
printf '%s' "$1"
}
_ni_print_cmd() {
_ni_line=''
for _ni_a in "$@"; do
_ni_q=$(_ni_quote_arg "$_ni_a")
if [ -z "$_ni_line" ]; then
_ni_line=$_ni_q
else
_ni_line="$_ni_line $_ni_q"
fi
done
printf '[dry-run] %s\n' "$_ni_line"
}
_ni_parse_args() {
NI_ARGC=$#
case "${NI_DRY_RUN-}" in
1|true|TRUE|yes|YES|on|ON) NI_DRY_RUN=1 ;;
*) NI_DRY_RUN=0 ;;
esac
}
_ni_require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
_ni_err "Error: command not found: $1"
return 127
fi
}
_ni_run() {
[ $# -gt 0 ] || return 0
if [ "$NI_DRY_RUN" = '1' ]; then
_ni_print_cmd "$@"
return 0
fi
_ni_require_cmd "$1" || return $?
"$@"
}
_ni_find_project_root() {
_ni_d=$PWD
while :; do
if [ -f "$_ni_d/package.json" ]; then
printf '%s\n' "$_ni_d"
return 0
fi
if [ "$_ni_d" = '/' ]; then
break
fi
_ni_n=$(dirname "$_ni_d")
if [ "$_ni_n" = "$_ni_d" ]; then
break
fi
_ni_d=$_ni_n
done
return 1
}
_ni_detect_pm() {
_ni_d=$PWD
while :; do
if [ -f "$_ni_d/pnpm-lock.yaml" ]; then
printf 'pnpm\n'
return 0
fi
if [ -f "$_ni_d/bun.lockb" ] || [ -f "$_ni_d/bun.lock" ]; then
printf 'bun\n'
return 0
fi
if [ -f "$_ni_d/yarn.lock" ]; then
printf 'yarn\n'
return 0
fi
if [ -f "$_ni_d/package-lock.json" ] || [ -f "$_ni_d/npm-shrinkwrap.json" ]; then
printf 'npm\n'
return 0
fi
if [ "$_ni_d" = '/' ]; then
break
fi
_ni_n=$(dirname "$_ni_d")
if [ "$_ni_n" = "$_ni_d" ]; then
break
fi
_ni_d=$_ni_n
done
return 1
}
_ni_require_pm() {
_ni_pm=$(_ni_detect_pm)
if [ -n "$_ni_pm" ]; then
printf '%s\n' "$_ni_pm"
return 0
fi
_ni_err 'Error: no lockfile found, run `ni` first'
return 1
}
_ni_select_pm() {
_ni_require_cmd fzf || return $?
_ni_pm_options=''
for _ni_candidate in bun pnpm yarn npm; do
if command -v "$_ni_candidate" >/dev/null 2>&1; then
_ni_status_label='installed'
else
_ni_status_label='missing'
fi
_ni_option_line=$(printf '%-5s [%s]' "$_ni_candidate" "$_ni_status_label")
if [ -z "$_ni_pm_options" ]; then
_ni_pm_options=$_ni_option_line
else
_ni_pm_options=$(printf '%s\n%s' "$_ni_pm_options" "$_ni_option_line")
fi
done
_ni_selected_pm=$(printf '%s\n' "$_ni_pm_options" | fzf --height=40% --layout=reverse --prompt='pm> ')
_ni_status=$?
if [ "$_ni_status" -ne 0 ]; then
return "$_ni_status"
fi
_ni_selected_pm=$(printf '%s\n' "$_ni_selected_pm" | awk '{print $1}')
if [ -z "$_ni_selected_pm" ]; then
_ni_err 'Error: no package manager selected.'
return 1
fi
printf '%s\n' "$_ni_selected_pm"
}
_ni_detect_yarn_cmd() {
_ni_d=$PWD
while :; do
if [ -f "$_ni_d/.yarnrc.yml" ] || [ -d "$_ni_d/.yarn/releases" ]; then
printf 'up -i\n'
return 0
fi
if [ "$_ni_d" = '/' ]; then
break
fi
_ni_n=$(dirname "$_ni_d")
if [ "$_ni_n" = "$_ni_d" ]; then
break
fi
_ni_d=$_ni_n
done
if command -v yarn >/dev/null 2>&1; then
_ni_yarn_version=$(yarn --version 2>/dev/null)
case "$_ni_yarn_version" in
1.*) printf 'upgrade-interactive\n' ;;
*) printf 'up -i\n' ;;
esac
return 0
fi
printf 'upgrade-interactive\n'
}
ni() {
_ni_pm=$(_ni_detect_pm)
if [ -z "$_ni_pm" ]; then
_ni_err 'No lockfile found. Select a package manager for this install:'
_ni_pm=$(_ni_select_pm) || return $?
fi
_ni_parse_args "$@"
if [ "$NI_ARGC" -eq 0 ]; then
case "$_ni_pm" in
pnpm) _ni_run pnpm install ;;
yarn) _ni_run yarn install ;;
bun) _ni_run bun install ;;
npm) _ni_run npm install ;;
*)
_ni_err "Error: unsupported package manager: $_ni_pm"
return 1
;;
esac
return $?
fi
case "$_ni_pm" in
pnpm) _ni_run pnpm add "$@" ;;
yarn) _ni_run yarn add "$@" ;;
bun) _ni_run bun add "$@" ;;
npm) _ni_run npm install "$@" ;;
*)
_ni_err "Error: unsupported package manager: $_ni_pm"
return 1
;;
esac
}
nci() {
_ni_pm=$(_ni_detect_pm)
if [ -z "$_ni_pm" ]; then
_ni_err 'No lockfile found. Select a package manager for this install:'
_ni_pm=$(_ni_select_pm) || return $?
fi
_ni_parse_args "$@"
case "$_ni_pm" in
pnpm) _ni_run pnpm install --frozen-lockfile "$@" ;;
yarn) _ni_run yarn install --frozen-lockfile "$@" ;;
bun) _ni_run bun ci "$@" ;;
npm) _ni_run npm ci "$@" ;;
*)
_ni_err "Error: unsupported package manager: $_ni_pm"
return 1
;;
esac
}
nr() {
_ni_pm=$(_ni_require_pm) || return $?
_ni_parse_args "$@"
if [ "$NI_ARGC" -gt 0 ]; then
_ni_run "$_ni_pm" run "$@"
return $?
fi
if [ "$NI_DRY_RUN" = '1' ]; then
_ni_print_cmd "$_ni_pm" run
return 0
fi
_ni_root=$(_ni_find_project_root) || {
_ni_err 'Error: package.json not found.'
return 1
}
_ni_require_cmd jq || return $?
_ni_require_cmd fzf || return $?
_ni_require_cmd column || return $?
_ni_scripts=$(jq -r 'if (.scripts|type) == "object" then (.scripts|to_entries[]|"\(.key)\t\(.value)") else empty end' "$_ni_root/package.json" 2>/dev/null)
if [ -z "$_ni_scripts" ]; then
_ni_err 'Error: no scripts found in package.json.'
return 1
fi
# 1. Pipe through `column` to align vertically
# 2. Removed --delimiter and --with-nth (fzf defaults work better with aligned spaces)
_ni_selected=$(printf '%s\n' "$_ni_scripts" | column -t -s "$(printf '\t')" | fzf --height=40% --layout=reverse --prompt='script> ')
_ni_status=$?
if [ "$_ni_status" -ne 0 ]; then
return "$_ni_status"
fi
# 3. Use awk to grab the first word (script name), as cut -f1 relies on tabs
_ni_script=$(printf '%s' "$_ni_selected" | awk '{print $1}')
if [ -z "$_ni_script" ]; then
_ni_err 'Error: no script selected.'
return 1
fi
_ni_run "$_ni_pm" run "$_ni_script"
}
nup() {
_ni_pm=$(_ni_require_pm) || return $?
_ni_parse_args "$@"
case "$_ni_pm" in
pnpm)
_ni_run pnpm update -i "$@"
;;
bun)
_ni_run bun update -i "$@"
;;
yarn)
_ni_yarn_cmd=$(_ni_detect_yarn_cmd)
case "$_ni_yarn_cmd" in
'upgrade-interactive') _ni_run yarn upgrade-interactive "$@" ;;
*) _ni_run yarn up -i "$@" ;;
esac
;;
npm)
_ni_run npm upgrade "$@"
;;
*)
_ni_err "Error: unsupported package manager: $_ni_pm"
return 1
;;
esac
}
@subframe7536
Copy link
Author

subframe7536 commented Feb 14, 2026

Requirements: jq and fzf
Install: source /path/to/ni.zsh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment