Skip to content

Commit 0705654

Browse files
committed
feat: shell integration
1 parent 29f38b9 commit 0705654

8 files changed

Lines changed: 485 additions & 3 deletions

File tree

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,74 @@ Logs are stored in `~/.spm2/logs/`. Rotation:
9696
- Removes logs older than 3 days
9797
- Run `spm rotate start` for a background watcher
9898

99-
## Shell Completions
99+
## Shell Integration
100100

101-
See [Yukaii/dotfiles](https://github.com/Yukaii/dotfiles/commit/569b50824c19340cefb308f385168492418f98e7) for shell completion setup.
101+
Completions require [jq](https://jqlang.github.io/jq/). App selector also requires [fzf](https://github.com/junegunn/fzf).
102+
103+
### Bash
104+
105+
**Completions** — Source the script (requires [bash-completion](https://github.com/scop/bash-completion); on macOS: `brew install bash-completion@2`):
106+
107+
```bash
108+
# After npm install -g @hackmd/spm:
109+
source $(npm root -g)/@hackmd/spm/completions/bash/spm.bash
110+
# Or add to ~/.bashrc:
111+
# source /path/to/spm/completions/bash/spm.bash
112+
```
113+
114+
**App selector** — Source the script:
115+
116+
```bash
117+
source $(npm root -g)/@hackmd/spm/completions/bash/spm_app_selector.sh
118+
# Then:
119+
spm_app_selector start
120+
spm_app_selector --appName=api restart
121+
```
122+
123+
### Zsh
124+
125+
**Completions** — Add the completion dir to `fpath` and ensure compinit runs:
126+
127+
```bash
128+
# After npm install -g @hackmd/spm:
129+
fpath=($(npm root -g)/@hackmd/spm/completions/zsh $fpath)
130+
compinit
131+
# Or copy to a dir in $fpath:
132+
mkdir -p ~/.zsh/completions
133+
cp $(npm root -g)/@hackmd/spm/completions/zsh/_spm ~/.zsh/completions/
134+
# Add to ~/.zshrc: fpath=(~/.zsh/completions $fpath)
135+
```
136+
137+
**App selector** — Source the script:
138+
139+
```bash
140+
source $(npm root -g)/@hackmd/spm/completions/zsh/spm_app_selector.zsh
141+
spm_app_selector start
142+
```
143+
144+
### Fish
145+
146+
**Completions** — Copy to your Fish config:
147+
148+
```bash
149+
mkdir -p ~/.config/fish/completions
150+
cp $(npm root -g)/@hackmd/spm/completions/fish/spm.fish ~/.config/fish/completions/
151+
```
152+
153+
**App selector** — Copy the function:
154+
155+
```bash
156+
mkdir -p ~/.config/fish/functions
157+
cp $(npm root -g)/@hackmd/spm/completions/fish/functions/spm_app_selector.fish ~/.config/fish/functions/
158+
```
159+
160+
Usage:
161+
162+
```fish
163+
spm_app_selector start
164+
spm_app_selector --appName=api restart
165+
spm_app_selector --config=ecosystem.config.js logs
166+
```
102167

103168
## License
104169

completions/bash/spm.bash

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Bash completion for spm
2+
# Usage: source this file or copy to /etc/bash_completion.d/spm
3+
# Requires: bash-completion (Linux) or bash-completion2 (macOS: brew install bash-completion@2)
4+
5+
_spm_services() {
6+
spm jlist 2>/dev/null | jq -r '.[].name'
7+
}
8+
9+
_spm() {
10+
local cur prev words cword
11+
if declare -f _init_completion &>/dev/null; then
12+
_init_completion -s || return
13+
else
14+
words=("${COMP_WORDS[@]}")
15+
cword=$COMP_CWORD
16+
cur="${words[cword]}"
17+
prev="${words[cword-1]}"
18+
fi
19+
20+
local subcommands="start stop kill logs list jlist restart flush rotate"
21+
local rotate_subcommands="start watch stop"
22+
23+
case $prev in
24+
-c|--config)
25+
if declare -f _filedir &>/dev/null; then
26+
_filedir
27+
else
28+
COMPREPLY=($(compgen -f -X '!*.@(js|mjs|cjs)' -- "$cur"))
29+
fi
30+
return
31+
;;
32+
-s|--signal)
33+
COMPREPLY=($(compgen -W "SIGTERM SIGINT SIGKILL" -- "$cur"))
34+
return
35+
;;
36+
-n|--lines)
37+
return
38+
;;
39+
-f|--filter)
40+
return
41+
;;
42+
--cleanup-interval)
43+
return
44+
;;
45+
spm)
46+
COMPREPLY=($(compgen -W "$subcommands -c --config -v --verbose -h --help" -- "$cur"))
47+
return
48+
;;
49+
rotate)
50+
COMPREPLY=($(compgen -W "$rotate_subcommands" -- "$cur"))
51+
return
52+
;;
53+
esac
54+
55+
# Check if we're completing after a subcommand that takes a service name
56+
for ((i = 1; i < cword; i++)); do
57+
case ${words[i]} in
58+
start|stop|kill|restart|logs|flush)
59+
COMPREPLY=($(compgen -W "$(_spm_services)" -- "$cur"))
60+
return
61+
;;
62+
rotate)
63+
if [[ $i -eq $((cword - 1)) ]]; then
64+
COMPREPLY=($(compgen -W "$rotate_subcommands" -- "$cur"))
65+
fi
66+
return
67+
;;
68+
esac
69+
done
70+
71+
# Check for logs options
72+
if [[ " ${words[@]} " =~ " logs " ]]; then
73+
case $cur in
74+
-*) COMPREPLY=($(compgen -W "-t --tail -n --lines -f --filter -h --help" -- "$cur")) ;;
75+
*) COMPREPLY=($(compgen -W "$(_spm_services)" -- "$cur")) ;;
76+
esac
77+
return
78+
fi
79+
80+
# Check for stop/kill options
81+
if [[ " ${words[@]} " =~ " stop " ]] || [[ " ${words[@]} " =~ " kill " ]]; then
82+
case $cur in
83+
-*) COMPREPLY=($(compgen -W "-s --signal -h --help" -- "$cur")) ;;
84+
*) COMPREPLY=($(compgen -W "$(_spm_services)" -- "$cur")) ;;
85+
esac
86+
return
87+
fi
88+
89+
# Default: complete subcommands or global options
90+
COMPREPLY=($(compgen -W "$subcommands -c --config -v --verbose -h --help" -- "$cur"))
91+
}
92+
93+
complete -F _spm spm
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
# Interactive service picker for spm. Requires fzf and jq.
3+
# Usage: source this file, then run spm_app_selector start|restart|logs|stop|flush
4+
5+
spm_app_selector() {
6+
local command=""
7+
local config=""
8+
local app_name=""
9+
10+
while [[ $# -gt 0 ]]; do
11+
case $1 in
12+
--config=*)
13+
config="${1#*=}"
14+
shift
15+
;;
16+
--appName=*)
17+
app_name="${1#*=}"
18+
shift
19+
;;
20+
-h|--help)
21+
echo "Usage: spm_app_selector [OPTIONS] COMMAND"
22+
echo ""
23+
echo "Options:"
24+
echo " --appName=APP_NAME Specify the application name directly."
25+
echo " --config=CONFIG_FILE Specify the ecosystem configuration file."
26+
echo " -h, --help Display this help message."
27+
echo ""
28+
echo "Examples:"
29+
echo " spm_app_selector start"
30+
echo " spm_app_selector --appName=api restart"
31+
echo " spm_app_selector --config=ecosystem.config.js logs"
32+
return 0
33+
;;
34+
start|stop|kill|restart|logs|flush)
35+
command="$1"
36+
shift
37+
break
38+
;;
39+
*)
40+
echo "Unknown option or command: $1"
41+
return 1
42+
;;
43+
esac
44+
done
45+
46+
if [[ -z "$command" ]]; then
47+
echo "Usage: spm_app_selector [OPTIONS] COMMAND"
48+
echo "COMMAND must be one of: start stop kill restart logs flush"
49+
return 1
50+
fi
51+
52+
if [[ -z "$config" ]]; then
53+
if [[ -f ecosystem.config.js ]]; then
54+
config="ecosystem.config.js"
55+
elif [[ -f ecosystem.config.cjs ]]; then
56+
config="ecosystem.config.cjs"
57+
elif [[ -f ecosystem.custom.config.js ]]; then
58+
config="ecosystem.custom.config.js"
59+
fi
60+
fi
61+
62+
if [[ -z "$config" ]]; then
63+
echo "No ecosystem configuration file found."
64+
return 1
65+
fi
66+
67+
if [[ -z "$app_name" ]]; then
68+
app_name=$(spm ${config:+--config "$config"} jlist 2>/dev/null | jq -r '.[].name' | fzf)
69+
fi
70+
71+
if [[ -n "$app_name" ]]; then
72+
if [[ -n "$config" ]]; then
73+
spm --config "$config" "$command" "$app_name"
74+
else
75+
spm "$command" "$app_name"
76+
fi
77+
else
78+
echo "No application selected."
79+
return 1
80+
fi
81+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function spm_app_selector
2+
argparse 'h/help' 'appName=' 'config=' -- $argv
3+
or return
4+
5+
if set -q _flag_help
6+
echo "Usage: spm_app_selector [OPTIONS] -- [COMMAND]"
7+
echo ""
8+
echo "Options:"
9+
echo " --appName=APP_NAME Specify the application name directly."
10+
echo " --config=CONFIG_FILE Specify the ecosystem configuration file (ecosystem.config.js or ecosystem.custom.config.js)."
11+
echo " -h, --help Display this help message and exit."
12+
echo ""
13+
echo "Examples:"
14+
echo " spm_app_selector start"
15+
echo " spm_app_selector --appName=api restart"
16+
echo " spm_app_selector --config=ecosystem.config.js logs"
17+
return
18+
end
19+
20+
if not set -q _flag_config
21+
if test -f ecosystem.config.js
22+
set _flag_config ecosystem.config.js
23+
else if test -f ecosystem.config.cjs
24+
set _flag_config ecosystem.config.cjs
25+
else if test -f ecosystem.custom.config.js
26+
set _flag_config ecosystem.custom.config.js
27+
end
28+
end
29+
30+
if not set -q _flag_config
31+
echo "No ecosystem configuration file found."
32+
return
33+
end
34+
35+
set -l command $argv[1]
36+
if test -z "$command"
37+
echo "Usage: spm_app_selector [OPTIONS] COMMAND"
38+
echo "COMMAND must be one of: start stop kill restart logs flush"
39+
return 1
40+
end
41+
42+
if set -q _flag_appName
43+
set APP_NAME $_flag_appName
44+
else
45+
set spm_args
46+
if set -q _flag_config
47+
set -a spm_args --config $_flag_config
48+
end
49+
set APP_NAME (spm $spm_args jlist 2>/dev/null | jq -r '.[].name' | fzf)
50+
end
51+
52+
if test -n "$APP_NAME"
53+
set spm_args
54+
if set -q _flag_config
55+
set -a spm_args --config $_flag_config
56+
end
57+
spm $spm_args $command $APP_NAME
58+
else
59+
echo "No application selected."
60+
end
61+
end

completions/fish/spm.fish

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Disable file completions globally for spm
2+
complete -c spm -f
3+
4+
# Helper function to check if spm has not received a subcommand
5+
function __fish_spm_no_subcommand --description "Check if spm has not received a subcommand"
6+
for i in (commandline -opc)
7+
if contains -- $i start stop kill logs list jlist restart flush rotate
8+
return 1
9+
end
10+
end
11+
return 0
12+
end
13+
14+
# Dynamic service names completion using spm jlist
15+
function __fish_spm_process_names --description "Fetch list of spm services"
16+
spm jlist 2>/dev/null | jq -r '.[].name'
17+
end
18+
19+
# Root-level subcommands with descriptions
20+
complete -c spm -n '__fish_spm_no_subcommand' -a "start" -d "Start service instance(s)"
21+
complete -c spm -n '__fish_spm_no_subcommand' -a "stop" -d "Stop service instance(s)"
22+
complete -c spm -n '__fish_spm_no_subcommand' -a "kill" -d "Alias for 'stop'"
23+
complete -c spm -n '__fish_spm_no_subcommand' -a "logs" -d "Display logs for service instance(s)"
24+
complete -c spm -n '__fish_spm_no_subcommand' -a "list" -d "List services with running PIDs"
25+
complete -c spm -n '__fish_spm_no_subcommand' -a "jlist" -d "List services in JSON format"
26+
complete -c spm -n '__fish_spm_no_subcommand' -a "restart" -d "Restart service instance(s)"
27+
complete -c spm -n '__fish_spm_no_subcommand' -a "flush" -d "Clear log file contents"
28+
complete -c spm -n '__fish_spm_no_subcommand' -a "rotate" -d "Manage log rotation"
29+
30+
# Dynamic service name completion for commands that take a service argument
31+
for cmd in start stop restart logs flush kill
32+
complete -c spm -n "__fish_seen_subcommand_from $cmd" -a "(__fish_spm_process_names)" -d "Service names"
33+
end
34+
35+
# Subcommand-specific completions for the logs command
36+
complete -c spm -n '__fish_seen_subcommand_from logs' -a '--tail' -d "Tail log files in real time"
37+
complete -c spm -n '__fish_seen_subcommand_from logs' -a '-n --lines' -d "Number of lines to show"
38+
complete -c spm -n '__fish_seen_subcommand_from logs' -a '-f --filter' -d "Filter logs by pattern"
39+
complete -c spm -n '__fish_seen_subcommand_from logs' -a '-h --help' -d "Show help"
40+
41+
# Completions for the rotate subcommand group
42+
complete -c spm -n '__fish_seen_subcommand_from rotate' -a "start" -d "Perform log rotation and spawn rotate-watch process"
43+
complete -c spm -n '__fish_seen_subcommand_from rotate' -a "watch" -d "Continuously watch and rotate logs"
44+
complete -c spm -n '__fish_seen_subcommand_from rotate' -a "stop" -d "Stop the rotate-watch process"

0 commit comments

Comments
 (0)