-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpackage-scripts-autocomplete.plugin.zsh
More file actions
133 lines (115 loc) · 4.62 KB
/
Copy pathpackage-scripts-autocomplete.plugin.zsh
File metadata and controls
133 lines (115 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# pkg-scripts
# Oh My Zsh plugin: tab-complete package.json "scripts" for npm/yarn/pnpm/bun.
#
# This plugin ONLY provides completion suggestions. It never executes any
# script, never makes network calls, and only reads the local package.json.
#
# Config (set before the plugin loads, e.g. earlier in ~/.zshrc):
# PJS_SEARCH_UPWARD=1 # walk up parent dirs to find package.json (default: 1)
# ---- defaults -------------------------------------------------------------
: ${PJS_SEARCH_UPWARD:=1}
# Interactive, highlighted menu (arrow keys / tab to move) for these commands
# only — like `git checkout <branch>`. Scoped so it does not change completion
# behaviour for any other command. To make menu selection global instead, set
# `zstyle ':completion:*' menu select` in your ~/.zshrc.
zstyle ':completion:*:*:(npm|yarn|pnpm|bun):*' menu select
# ---- per-file cache (path + mtime) ---------------------------------------
typeset -g __pjs_cache_file=""
typeset -g __pjs_cache_mtime=""
typeset -ga __pjs_cache_lines=()
# Locate the nearest package.json (current dir, optionally walking upward).
__pjs_find_package_json() {
local dir="$PWD"
if [[ "$PJS_SEARCH_UPWARD" == "1" ]]; then
while [[ -n "$dir" ]]; do
[[ -f "$dir/package.json" ]] && { print -r -- "$dir/package.json"; return 0; }
[[ "$dir" == "/" ]] && break
dir="${dir:h}" # :h = dirname
done
return 1
fi
[[ -f "$dir/package.json" ]] && print -r -- "$dir/package.json"
}
# Portable mtime (BSD/macOS `stat -f`, GNU/Linux `stat -c`).
__pjs_file_mtime() {
stat -f '%m' "$1" 2>/dev/null || stat -c '%Y' "$1" 2>/dev/null
}
# Populate __pjs_cache_lines with "name<TAB>command" rows. Cached by mtime.
__pjs_load_scripts() {
local file mtime
file="$(__pjs_find_package_json)" || return 1
[[ -n "$file" ]] || return 1
mtime="$(__pjs_file_mtime "$file")"
if [[ "$file" == "$__pjs_cache_file" && "$mtime" == "$__pjs_cache_mtime" ]]; then
(( ${#__pjs_cache_lines[@]} )) && return 0 || return 1
fi
__pjs_cache_file="$file"
__pjs_cache_mtime="$mtime"
__pjs_cache_lines=()
command -v node >/dev/null 2>&1 || return 1
# node only PARSES JSON and prints "name<TAB>command". It never runs anything.
local -a lines
lines=("${(@f)$(node -e '
const fs = require("fs");
try {
const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const scripts = (pkg && typeof pkg.scripts === "object" && pkg.scripts) || {};
for (const [name, cmd] of Object.entries(scripts)) {
process.stdout.write(name + "\t" + String(cmd).replace(/\s+/g, " ").trim() + "\n");
}
} catch { process.exit(0); }
' "$file" 2>/dev/null)}")
__pjs_cache_lines=("${lines[@]}")
(( ${#__pjs_cache_lines[@]} ))
}
# Build "value:description" rows for _describe.
# $1 = optional prefix prepended to the value (e.g. "run ").
# Colons inside script names (e.g. dev:ios) are escaped so _describe does not
# treat them as the value/description separator.
__pjs_build_describe_rows() {
local prefix="$1" line name desc
reply=()
for line in "${__pjs_cache_lines[@]}"; do
name="${line%%$'\t'*}"
desc="${line#*$'\t'}"
reply+=("${prefix}${name//:/\\:}:${desc:-package.json script}")
done
}
# Completion: bare script names. Renders "name -- command".
__pjs_complete_script_names() {
__pjs_load_scripts || return 1
local -a reply
__pjs_build_describe_rows ""
(( ${#reply[@]} )) && _describe -t pkg-scripts 'package.json script' reply
}
# Completion: "run <script>" pairs (npm, where the bare name is not runnable).
# -Q stops zsh escaping the space, so it inserts `run build` as two words.
__pjs_complete_run_script_pairs() {
__pjs_load_scripts || return 1
local -a reply
__pjs_build_describe_rows "run "
(( ${#reply[@]} )) && _describe -t pkg-scripts 'package.json script' reply -- -Q
}
# ---- per-command completion entry points ---------------------------------
# npm: scripts are only runnable via `npm run <script>`, so suggest the pair.
_pjs_complete_npm() {
if (( CURRENT == 2 )); then
__pjs_complete_run_script_pairs && return 0
elif [[ "${words[2]}" == (run|run-script) ]] && (( CURRENT == 3 )); then
__pjs_complete_script_names && return 0
fi
_default
}
# yarn / pnpm / bun: `<cmd> <script>` runs directly, and `<cmd> run <script>` too.
_pjs_complete_yarn_pnpm_bun() {
if (( CURRENT == 2 )); then
__pjs_complete_script_names && return 0
elif [[ "${words[2]}" == "run" ]] && (( CURRENT == 3 )); then
__pjs_complete_script_names && return 0
fi
_default
}
compdef _pjs_complete_npm npm
compdef _pjs_complete_yarn_pnpm_bun yarn
compdef _pjs_complete_yarn_pnpm_bun pnpm
compdef _pjs_complete_yarn_pnpm_bun bun