1+ #! /usr/bin/env bash
2+
3+ # Generate the enhanced pnpm completion script
4+ cat << 'EOF '
5+ #compdef pnpm
6+
7+ if command -v pnpm-shell-completion &> /dev/null; then
8+ pnpm_comp_bin="$(which pnpm-shell-completion)"
9+ else
10+ pnpm_comp_bin="$(dirname $0)/pnpm-shell-completion"
11+ fi
12+
13+ # Function to check if a command has Tab-powered completions
14+ _has_tab_completion() {
15+ local cmd="$1"
16+
17+ # The most reliable method: Check if running the command's completion outputs a directive
18+ # Tab completions end with a line like ":4" to indicate the completion directive
19+ # Use timeout to prevent hanging on commands that don't support completions
20+ if timeout 1 pnpm exec $cmd complete -- "" 2>/dev/null | grep -q ':[0-9]\+$'; then
21+ return 0 # Success - command has Tab completions
22+ fi
23+
24+ # No completion found
25+ return 1 # Failure - command doesn't have Tab completions
26+ }
27+
28+ # Function to get completions from a Tab-powered command
29+ _get_tab_completions() {
30+ local cmd="$1"
31+ shift
32+ local args=("$@")
33+
34+ # Standard completion method with timeout to prevent hanging
35+ timeout 1 pnpm exec $cmd complete -- "${args[@]}" 2>/dev/null
36+ return $?
37+ }
38+
39+ _pnpm() {
40+ typeset -A opt_args
41+ local cmd_index=1
42+ local has_custom_completion=0
43+ local custom_cmd=""
44+
45+ # Check if we have command arguments beyond "pnpm"
46+ if (( CURRENT > 1 )); then
47+ # The first argument after pnpm might be a command with its own completion
48+ custom_cmd="${words[2]}"
49+
50+ # Check for workspace-specific flags that would shift the command position
51+ if [[ "${words[2]}" == "--filter" || "${words[2]}" == "-F" ]]; then
52+ # The command comes after the filter and value
53+ if (( CURRENT > 3 )); then
54+ custom_cmd="${words[4]}"
55+ cmd_index=4
56+ else
57+ custom_cmd=""
58+ fi
59+ fi
60+
61+ # Check if the command has Tab completions
62+ if [[ -n "$custom_cmd" ]] && _has_tab_completion "$custom_cmd"; then
63+ has_custom_completion=1
64+ fi
65+ fi
66+
67+ # If we found a command with Tab completions and we're trying to complete its arguments
68+ if [[ $has_custom_completion -eq 1 ]] && (( CURRENT > cmd_index )); then
69+ # Extract the arguments for the custom command
70+ local cmd_args=("${words[@]:cmd_index}")
71+
72+ # Get Tab completions for this command
73+ _get_tab_completions "$custom_cmd" "${cmd_args[@]}"
74+ return 0
75+ fi
76+
77+ # Original pnpm completion logic
78+ _arguments \
79+ '(--filter -F)'{--filter,-F}'=:flag:->filter' \
80+ ':command:->scripts' \
81+ '*:: :->command_args'
82+
83+ local target_pkg=${opt_args[--filter]:-$opt_args[-F]}
84+
85+ case $state in
86+ filter)
87+ if [[ -f ./pnpm-workspace.yaml ]]; then
88+ _values 'filter packages' $(FEATURE=filter $pnpm_comp_bin)
89+ fi
90+ ;;
91+ scripts)
92+ _values 'scripts' $(FEATURE=scripts TARGET_PKG=$target_pkg ZSH=true $pnpm_comp_bin) \
93+ add remove install update publish
94+ ;;
95+ command_args)
96+ local cmd=$(FEATURE=pnpm_cmd $pnpm_comp_bin $words)
97+ case $cmd in
98+ add)
99+ _arguments \
100+ '(--global -g)'{--global,-g}'[Install as a global package]' \
101+ '(--save-dev -D)'{--save-dev,-D}'[Save package to your `devDependencies`]' \
102+ '--save-peer[Save package to your `peerDependencies` and `devDependencies`]'
103+ ;;
104+ install|i)
105+ _arguments \
106+ '(--dev -D)'{--dev,-D}'[Only `devDependencies` are installed regardless of the `NODE_ENV`]' \
107+ '--fix-lockfile[Fix broken lockfile entries automatically]' \
108+ '--force[Force reinstall dependencies]' \
109+ "--ignore-scripts[Don't run lifecycle scripts]" \
110+ '--lockfile-only[Dependencies are not downloaded. Only `pnpm-lock.yaml` is updated]' \
111+ '--no-optional[`optionalDependencies` are not installed]' \
112+ '--offline[Trigger an error if any required dependencies are not available in local store]' \
113+ '--prefer-offline[Skip staleness checks for cached data, but request missing data from the server]' \
114+ '(--prod -P)'{--prod,-P}"[Packages in \`devDependencies\` won't be installed]"
115+ ;;
116+ remove|rm|why)
117+ if [[ -f ./package.json ]]; then
118+ _values 'deps' $(FEATURE=deps TARGET_PKG=$target_pkg $pnpm_comp_bin)
119+ fi
120+ ;;
121+ update|upgrade|up)
122+ _arguments \
123+ '(--dev -D)'{--dev,-D}'[Update packages only in "devDependencies"]' \
124+ '(--global -g)'{--global,-g}'[Update globally installed packages]' \
125+ '(--interactive -i)'{--interactive,-i}'[Show outdated dependencies and select which ones to update]' \
126+ '(--latest -L)'{--latest,-L}'[Ignore version ranges in package.json]' \
127+ "--no-optional[Don't update packages in \`optionalDependencies\`]" \
128+ '(--prod -P)'{--prod,-P}'[Update packages only in "dependencies" and "optionalDependencies"]' \
129+ '(--recursive -r)'{--recursive,-r}'[Update in every package found in subdirectories or every workspace package]'
130+ if [[ -f ./package.json ]]; then
131+ _values 'deps' $(FEATURE=deps TARGET_PKG=$target_pkg $pnpm_comp_bin)
132+ fi
133+ ;;
134+ publish)
135+ _arguments \
136+ '--access=[Tells the registry whether this package should be published as public or restricted]: :(public restricted)' \
137+ '--dry-run[Does everything a publish would do except actually publishing to the registry]' \
138+ '--force[Packages are proceeded to be published even if their current version is already in the registry]' \
139+ '--ignore-scripts[Ignores any publish related lifecycle scripts (prepublishOnly, postpublish, and the like)]' \
140+ "--no-git-checks[Don't check if current branch is your publish branch, clean, and up to date]" \
141+ '--otp[Specify a one-time password]' \
142+ '--publish-branch[Sets branch name to publish]' \
143+ '(--recursive -r)'{--recursive,-r}'[Publish all packages from the workspace]' \
144+ '--tag=[Registers the published package with the given tag]'
145+ ;;
146+ run)
147+ if [[ -f ./package.json ]]; then
148+ _values 'scripts' $(FEATURE=scripts TARGET_PKG=$target_pkg ZSH=true $pnpm_comp_bin)
149+ fi
150+ ;;
151+ *)
152+ _files
153+ esac
154+ esac
155+ }
156+
157+ compdef _pnpm pnpm
158+ EOF
0 commit comments