opencode-homebrew-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/agents/brew.md +146 -0
- package/package.json +22 -0
- package/src/AGENTS.md +172 -0
- package/src/scripts/brew-analyze.sh +373 -0
- package/src/scripts/brew-deps.sh +140 -0
- package/src/scripts/brew-env.sh +288 -0
- package/src/scripts/brew-search.sh +150 -0
- package/src/scripts/brew-template.sh +263 -0
- package/src/scripts/package.json +21 -0
- package/src/skills/homebrew-agent/SKILL.md +203 -0
- package/src/skills/homebrew-tap/SKILL.md +97 -0
- package/src/templates/taps/01-full-ci/AGENTS.md +71 -0
- package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/autobump.yml +24 -0
- package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/publish.yml +33 -0
- package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/tests.yml +33 -0
- package/src/templates/taps/01-full-ci/tap-structure/Formula/_formula.rb +31 -0
- package/src/templates/taps/01-full-ci/tap-structure/README.md +27 -0
- package/src/templates/taps/01-full-ci/tap-structure/cmd/.gitkeep +0 -0
- package/src/templates/taps/01-full-ci/tap-structure/formula_renames.json +1 -0
- package/src/templates/taps/01-full-ci/tap-structure/lib/.gitkeep +0 -0
- package/src/templates/taps/01-full-ci/tap-structure/require/.gitkeep +0 -0
- package/src/templates/taps/01-full-ci/tap-structure/tap_migrations.json +1 -0
- package/src/templates/taps/01-full-ci/template.md +45 -0
- package/src/templates/taps/02-root-level/AGENTS.md +72 -0
- package/src/templates/taps/02-root-level/tap-structure/README.md +27 -0
- package/src/templates/taps/02-root-level/tap-structure/_formula.rb +29 -0
- package/src/templates/taps/02-root-level/template.md +35 -0
- package/src/templates/taps/03-simple-script/AGENTS.md +71 -0
- package/src/templates/taps/03-simple-script/tap-structure/Formula/_formula.rb +27 -0
- package/src/templates/taps/03-simple-script/tap-structure/README.md +27 -0
- package/src/templates/taps/03-simple-script/tap-structure/scripts/release.rb +46 -0
- package/src/templates/taps/03-simple-script/template.md +41 -0
- package/src/templates/taps/04-python-venv/AGENTS.md +83 -0
- package/src/templates/taps/04-python-venv/tap-structure/Formula/_formula.rb +57 -0
- package/src/templates/taps/04-python-venv/tap-structure/README.md +27 -0
- package/src/templates/taps/04-python-venv/tap-structure/scripts/release.rb +44 -0
- package/src/templates/taps/04-python-venv/template.md +58 -0
- package/src/templates/taps/05-node-npm/AGENTS.md +90 -0
- package/src/templates/taps/05-node-npm/tap-structure/Formula/_formula.rb +46 -0
- package/src/templates/taps/05-node-npm/tap-structure/README.md +27 -0
- package/src/templates/taps/05-node-npm/tap-structure/scripts/release.rb +40 -0
- package/src/templates/taps/05-node-npm/template.md +74 -0
- package/src/templates/taps/06-keg-only/AGENTS.md +82 -0
- package/src/templates/taps/06-keg-only/tap-structure/Formula/_formula.rb +45 -0
- package/src/templates/taps/06-keg-only/tap-structure/README.md +27 -0
- package/src/templates/taps/06-keg-only/template.md +60 -0
- package/src/templates/taps/07-cask-only/AGENTS.md +97 -0
- package/src/templates/taps/07-cask-only/tap-structure/Casks/_app.rb +26 -0
- package/src/templates/taps/07-cask-only/tap-structure/README.md +27 -0
- package/src/templates/taps/07-cask-only/template.md +58 -0
- package/src/templates/taps/08-go-binary/AGENTS.md +86 -0
- package/src/templates/taps/08-go-binary/tap-structure/Formula/_formula.rb +40 -0
- package/src/templates/taps/08-go-binary/tap-structure/README.md +27 -0
- package/src/templates/taps/08-go-binary/tap-structure/scripts/release.rb +38 -0
- package/src/templates/taps/08-go-binary/template.md +60 -0
- package/src/workflows/analyze-source.sh +124 -0
- package/src/workflows/convert-npm-to-bun.sh +112 -0
- package/src/workflows/create-tap.sh +196 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# scripts/brew-env.sh
|
|
5
|
+
# Inspect the Homebrew environment and output a formatted report.
|
|
6
|
+
|
|
7
|
+
PROG="brew-env.sh"
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<EOF
|
|
11
|
+
Usage: ${PROG} [OPTION]
|
|
12
|
+
|
|
13
|
+
Inspect the Homebrew environment.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--config Show only the brew config section
|
|
17
|
+
--taps Show only tapped repositories
|
|
18
|
+
--cellar Show only Cellar contents summary
|
|
19
|
+
--ruby Show only Ruby version info
|
|
20
|
+
--json Output full report as JSON
|
|
21
|
+
--help Show this help message and exit
|
|
22
|
+
|
|
23
|
+
With no arguments, prints a full human-readable report.
|
|
24
|
+
EOF
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Diagnostics go to stderr
|
|
28
|
+
die() {
|
|
29
|
+
echo "${PROG}: error: $1" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Data collection
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
get_brew_config() {
|
|
38
|
+
if command -v brew >/dev/null 2>&1; then
|
|
39
|
+
brew config 2>/dev/null | grep -E '^[A-Z_]+:' || true
|
|
40
|
+
else
|
|
41
|
+
echo "brew not found"
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get_taps() {
|
|
46
|
+
if command -v brew >/dev/null 2>&1; then
|
|
47
|
+
brew tap 2>/dev/null || true
|
|
48
|
+
else
|
|
49
|
+
echo "brew not found"
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get_cellar() {
|
|
54
|
+
local cellar_path
|
|
55
|
+
if command -v brew >/dev/null 2>&1; then
|
|
56
|
+
cellar_path="$(brew --prefix 2>/dev/null)/Cellar"
|
|
57
|
+
else
|
|
58
|
+
cellar_path="/opt/homebrew/Cellar"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if [[ -d "${cellar_path}" ]]; then
|
|
62
|
+
ls -1 "${cellar_path}" 2>/dev/null || true
|
|
63
|
+
else
|
|
64
|
+
echo "Cellar not found at ${cellar_path}"
|
|
65
|
+
fi
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get_ruby_version() {
|
|
69
|
+
if command -v brew >/dev/null 2>&1; then
|
|
70
|
+
brew ruby --version 2>/dev/null || ruby --version 2>/dev/null || echo "Ruby not found"
|
|
71
|
+
else
|
|
72
|
+
ruby --version 2>/dev/null || echo "Ruby not found"
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get_prefix() {
|
|
77
|
+
if command -v brew >/dev/null 2>&1; then
|
|
78
|
+
brew --prefix 2>/dev/null || echo "unknown"
|
|
79
|
+
else
|
|
80
|
+
echo "brew not found"
|
|
81
|
+
fi
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get_github_api_status() {
|
|
85
|
+
local status
|
|
86
|
+
status=$(curl -sI https://api.github.com 2>/dev/null | head -n 1 | tr -d '\r')
|
|
87
|
+
if [[ -z "${status}" ]]; then
|
|
88
|
+
echo "unreachable"
|
|
89
|
+
else
|
|
90
|
+
echo "${status}"
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Output formatters
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
print_header() {
|
|
99
|
+
echo ""
|
|
100
|
+
echo "== $1 =="
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
print_kv() {
|
|
104
|
+
local key value
|
|
105
|
+
key="$1"
|
|
106
|
+
value="$2"
|
|
107
|
+
printf " %-30s %s\n" "${key}:" "${value}"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
output_human() {
|
|
111
|
+
local show_config=false
|
|
112
|
+
local show_taps=false
|
|
113
|
+
local show_cellar=false
|
|
114
|
+
local show_ruby=false
|
|
115
|
+
|
|
116
|
+
if [[ -z "${1:-}" ]]; then
|
|
117
|
+
show_config=true
|
|
118
|
+
show_taps=true
|
|
119
|
+
show_cellar=true
|
|
120
|
+
show_ruby=true
|
|
121
|
+
else
|
|
122
|
+
case "$1" in
|
|
123
|
+
--config) show_config=true ;;
|
|
124
|
+
--taps) show_taps=true ;;
|
|
125
|
+
--cellar) show_cellar=true ;;
|
|
126
|
+
--ruby) show_ruby=true ;;
|
|
127
|
+
esac
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [[ "${show_config}" == true ]]; then
|
|
131
|
+
print_header "Homebrew Config"
|
|
132
|
+
while IFS= read -r line; do
|
|
133
|
+
if [[ -n "${line}" ]]; then
|
|
134
|
+
local key value
|
|
135
|
+
key="${line%%:*}"
|
|
136
|
+
value="${line#*: }"
|
|
137
|
+
print_kv "${key}" "${value}"
|
|
138
|
+
fi
|
|
139
|
+
done <<< "$(get_brew_config)"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if [[ "${show_taps}" == true ]]; then
|
|
143
|
+
print_header "Tapped Repositories"
|
|
144
|
+
local taps
|
|
145
|
+
taps=$(get_taps)
|
|
146
|
+
if [[ -z "${taps}" ]]; then
|
|
147
|
+
echo " (none)"
|
|
148
|
+
else
|
|
149
|
+
while IFS= read -r line; do
|
|
150
|
+
echo " ${line}"
|
|
151
|
+
done <<< "${taps}"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if [[ "${show_cellar}" == true ]]; then
|
|
156
|
+
print_header "Cellar Contents"
|
|
157
|
+
local cellar
|
|
158
|
+
cellar=$(get_cellar)
|
|
159
|
+
if [[ -z "${cellar}" ]]; then
|
|
160
|
+
echo " (empty)"
|
|
161
|
+
else
|
|
162
|
+
while IFS= read -r line; do
|
|
163
|
+
echo " ${line}"
|
|
164
|
+
done <<< "${cellar}"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [[ "${show_ruby}" == true ]]; then
|
|
169
|
+
print_header "Ruby Version"
|
|
170
|
+
echo " $(get_ruby_version)"
|
|
171
|
+
print_header "Homebrew Prefix"
|
|
172
|
+
echo " $(get_prefix)"
|
|
173
|
+
print_header "GitHub API Access"
|
|
174
|
+
echo " $(get_github_api_status)"
|
|
175
|
+
fi
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Simple JSON string escaping helper
|
|
179
|
+
json_escape() {
|
|
180
|
+
local str="$1"
|
|
181
|
+
str="${str//\\/\\\\}"
|
|
182
|
+
str="${str//\"/\\\"}"
|
|
183
|
+
str="${str//$'\n'/\\n}"
|
|
184
|
+
str="${str//$'\r'/}"
|
|
185
|
+
printf '%s' "$str"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
output_json() {
|
|
189
|
+
local config_lines taps_lines cellar_lines ruby_version prefix github_status
|
|
190
|
+
config_lines=$(get_brew_config)
|
|
191
|
+
taps_lines=$(get_taps)
|
|
192
|
+
cellar_lines=$(get_cellar)
|
|
193
|
+
ruby_version=$(get_ruby_version)
|
|
194
|
+
prefix=$(get_prefix)
|
|
195
|
+
github_status=$(get_github_api_status)
|
|
196
|
+
|
|
197
|
+
echo "{"
|
|
198
|
+
|
|
199
|
+
# config object
|
|
200
|
+
echo ' "config": {'
|
|
201
|
+
local first=true
|
|
202
|
+
while IFS= read -r line; do
|
|
203
|
+
if [[ -n "${line}" ]]; then
|
|
204
|
+
local key value
|
|
205
|
+
key="${line%%:*}"
|
|
206
|
+
value="${line#*: }"
|
|
207
|
+
if [[ "${first}" == true ]]; then
|
|
208
|
+
first=false
|
|
209
|
+
else
|
|
210
|
+
echo ","
|
|
211
|
+
fi
|
|
212
|
+
printf ' "%s": "%s"' "$(json_escape "${key}")" "$(json_escape "${value}")"
|
|
213
|
+
fi
|
|
214
|
+
done <<< "${config_lines}"
|
|
215
|
+
echo ""
|
|
216
|
+
echo " },"
|
|
217
|
+
|
|
218
|
+
# taps array
|
|
219
|
+
echo ' "taps": ['
|
|
220
|
+
first=true
|
|
221
|
+
while IFS= read -r line; do
|
|
222
|
+
if [[ -n "${line}" ]]; then
|
|
223
|
+
if [[ "${first}" == true ]]; then
|
|
224
|
+
first=false
|
|
225
|
+
else
|
|
226
|
+
echo ","
|
|
227
|
+
fi
|
|
228
|
+
printf ' "%s"' "$(json_escape "${line}")"
|
|
229
|
+
fi
|
|
230
|
+
done <<< "${taps_lines}"
|
|
231
|
+
echo ""
|
|
232
|
+
echo " ],"
|
|
233
|
+
|
|
234
|
+
# cellar array
|
|
235
|
+
echo ' "cellar": ['
|
|
236
|
+
first=true
|
|
237
|
+
while IFS= read -r line; do
|
|
238
|
+
if [[ -n "${line}" ]]; then
|
|
239
|
+
if [[ "${first}" == true ]]; then
|
|
240
|
+
first=false
|
|
241
|
+
else
|
|
242
|
+
echo ","
|
|
243
|
+
fi
|
|
244
|
+
printf ' "%s"' "$(json_escape "${line}")"
|
|
245
|
+
fi
|
|
246
|
+
done <<< "${cellar_lines}"
|
|
247
|
+
echo ""
|
|
248
|
+
echo " ],"
|
|
249
|
+
|
|
250
|
+
# metadata
|
|
251
|
+
printf ' "ruby_version": "%s",\n' "$(json_escape "${ruby_version}")"
|
|
252
|
+
printf ' "prefix": "%s",\n' "$(json_escape "${prefix}")"
|
|
253
|
+
printf ' "github_api_status": "%s"\n' "$(json_escape "${github_status}")"
|
|
254
|
+
|
|
255
|
+
echo "}"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# Main
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
main() {
|
|
263
|
+
local arg="${1:-}"
|
|
264
|
+
|
|
265
|
+
case "${arg}" in
|
|
266
|
+
--help|-h)
|
|
267
|
+
usage
|
|
268
|
+
exit 0
|
|
269
|
+
;;
|
|
270
|
+
--json)
|
|
271
|
+
output_json
|
|
272
|
+
exit 0
|
|
273
|
+
;;
|
|
274
|
+
--config|--taps|--cellar|--ruby)
|
|
275
|
+
output_human "${arg}"
|
|
276
|
+
exit 0
|
|
277
|
+
;;
|
|
278
|
+
"")
|
|
279
|
+
output_human
|
|
280
|
+
exit 0
|
|
281
|
+
;;
|
|
282
|
+
*)
|
|
283
|
+
die "unknown option: ${arg}"
|
|
284
|
+
;;
|
|
285
|
+
esac
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
main "$@"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROG_NAME="$(basename "$0")"
|
|
5
|
+
|
|
6
|
+
usage() {
|
|
7
|
+
cat <<EOF
|
|
8
|
+
Usage: ${PROG_NAME} [options] <query>
|
|
9
|
+
|
|
10
|
+
Search Homebrew formulae and casks with rich output.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--formulae Search only formulae
|
|
14
|
+
--casks Search only casks
|
|
15
|
+
--desc Search descriptions only
|
|
16
|
+
--json Output results as JSON
|
|
17
|
+
--help Show this help message
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
${PROG_NAME} python
|
|
21
|
+
${PROG_NAME} --casks firefox
|
|
22
|
+
${PROG_NAME} --json python
|
|
23
|
+
EOF
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
search_formulae=true
|
|
27
|
+
search_casks=true
|
|
28
|
+
search_desc=true
|
|
29
|
+
json_output=false
|
|
30
|
+
query=""
|
|
31
|
+
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--help)
|
|
35
|
+
usage
|
|
36
|
+
exit 0
|
|
37
|
+
;;
|
|
38
|
+
--formulae)
|
|
39
|
+
search_formulae=true
|
|
40
|
+
search_casks=false
|
|
41
|
+
search_desc=false
|
|
42
|
+
;;
|
|
43
|
+
--casks)
|
|
44
|
+
search_formulae=false
|
|
45
|
+
search_casks=true
|
|
46
|
+
search_desc=false
|
|
47
|
+
;;
|
|
48
|
+
--desc)
|
|
49
|
+
search_formulae=false
|
|
50
|
+
search_casks=false
|
|
51
|
+
search_desc=true
|
|
52
|
+
;;
|
|
53
|
+
--json)
|
|
54
|
+
json_output=true
|
|
55
|
+
;;
|
|
56
|
+
--)
|
|
57
|
+
shift
|
|
58
|
+
break
|
|
59
|
+
;;
|
|
60
|
+
-*)
|
|
61
|
+
echo "Error: Unknown option $1" >&2
|
|
62
|
+
usage >&2
|
|
63
|
+
exit 1
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
if [[ -z "$query" ]]; then
|
|
67
|
+
query="$1"
|
|
68
|
+
else
|
|
69
|
+
query="$query $1"
|
|
70
|
+
fi
|
|
71
|
+
;;
|
|
72
|
+
esac
|
|
73
|
+
shift
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
if [[ -z "$query" ]]; then
|
|
77
|
+
usage >&2
|
|
78
|
+
echo >&2
|
|
79
|
+
brew search
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
search_formulae_fn() {
|
|
84
|
+
brew search --formula --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
search_casks_fn() {
|
|
88
|
+
brew search --casks --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
search_desc_fn() {
|
|
92
|
+
brew search --desc --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
results_formulae=""
|
|
96
|
+
results_casks=""
|
|
97
|
+
results_desc=""
|
|
98
|
+
|
|
99
|
+
if [[ "$search_formulae" == true ]]; then
|
|
100
|
+
results_formulae=$(search_formulae_fn)
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [[ "$search_casks" == true ]]; then
|
|
104
|
+
results_casks=$(search_casks_fn)
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [[ "$search_desc" == true ]]; then
|
|
108
|
+
results_desc=$(search_desc_fn)
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
if [[ -z "$results_formulae" && -z "$results_casks" && -z "$results_desc" ]]; then
|
|
112
|
+
if [[ "$json_output" == true ]]; then
|
|
113
|
+
echo '{"formulae":[],"casks":[],"descriptions":[]}'
|
|
114
|
+
else
|
|
115
|
+
echo "No matches found"
|
|
116
|
+
fi
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ "$json_output" == true ]]; then
|
|
121
|
+
json_formulae=$(printf '%s\n' "$results_formulae" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
|
|
122
|
+
json_casks=$(printf '%s\n' "$results_casks" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
|
|
123
|
+
json_descriptions=$(printf '%s\n' "$results_desc" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
|
|
124
|
+
|
|
125
|
+
echo "{\"formulae\":${json_formulae},\"casks\":${json_casks},\"descriptions\":${json_descriptions}}"
|
|
126
|
+
else
|
|
127
|
+
print_section() {
|
|
128
|
+
local title="$1"
|
|
129
|
+
local content="$2"
|
|
130
|
+
if [[ -n "$content" ]]; then
|
|
131
|
+
local count
|
|
132
|
+
count=$(echo "$content" | wc -l | tr -d ' ')
|
|
133
|
+
echo "==> ${title} (${count})"
|
|
134
|
+
echo "$content"
|
|
135
|
+
echo
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if [[ "$search_formulae" == true ]]; then
|
|
140
|
+
print_section "Formulae" "$results_formulae"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
if [[ "$search_casks" == true ]]; then
|
|
144
|
+
print_section "Casks" "$results_casks"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [[ "$search_desc" == true ]]; then
|
|
148
|
+
print_section "Description Matches" "$results_desc"
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
6
|
+
TEMPLATES_DIR="$PROJECT_ROOT/templates/taps"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<'EOF'
|
|
10
|
+
Usage: brew-template.sh <project-type> [options]
|
|
11
|
+
|
|
12
|
+
Recommend and apply the best Homebrew tap template for a given project type.
|
|
13
|
+
|
|
14
|
+
Project types:
|
|
15
|
+
go-binary Go CLI built from source
|
|
16
|
+
python-venv Python CLI with pip virtualenv
|
|
17
|
+
node-npm JS/TS CLI with npm or bun
|
|
18
|
+
simple-script Simple script + release script
|
|
19
|
+
full-ci Full CI suite (test-bot + bottles + autobump)
|
|
20
|
+
root-level Root-level .rb (minimal, 1-2 formulae)
|
|
21
|
+
keg-only System tool replacement (shadows macOS)
|
|
22
|
+
cask macOS GUI app (.app/.dmg)
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--name <formula> Formula class name (CamelCase, auto-converts kebab-case)
|
|
26
|
+
--tap <user/tapname> Tap identifier
|
|
27
|
+
--output <path> Where to create the tap structure (required)
|
|
28
|
+
--with-ci Run 'brew tap-new <user>/<tapname>' first
|
|
29
|
+
--convert-to-bun If node-npm, add a note about using bun
|
|
30
|
+
--list List all templates with descriptions
|
|
31
|
+
--help Show this message
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
brew-template.sh go-binary --name my-tool --output ./homebrew-mytool
|
|
35
|
+
brew-template.sh node-npm --name my-tool --output ./homebrew-mytool --convert-to-bun
|
|
36
|
+
brew-template.sh --list
|
|
37
|
+
EOF
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
list_templates() {
|
|
41
|
+
cat <<'EOF'
|
|
42
|
+
Available templates:
|
|
43
|
+
01-full-ci Full CI suite (test-bot + bottles + autobump)
|
|
44
|
+
02-root-level Root-level .rb (minimal, 1-2 formulae)
|
|
45
|
+
03-simple-script Simple script + release script
|
|
46
|
+
04-python-venv Python CLI with pip virtualenv
|
|
47
|
+
05-node-npm JS/TS CLI with npm or bun
|
|
48
|
+
06-keg-only System tool replacement (shadows macOS)
|
|
49
|
+
07-cask-only macOS GUI app (.app/.dmg)
|
|
50
|
+
08-go-binary Go CLI built from source
|
|
51
|
+
EOF
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
map_type_to_dir() {
|
|
55
|
+
local type_name="$1"
|
|
56
|
+
case "$type_name" in
|
|
57
|
+
full-ci) echo "01-full-ci" ;;
|
|
58
|
+
root-level) echo "02-root-level" ;;
|
|
59
|
+
simple-script) echo "03-simple-script" ;;
|
|
60
|
+
python-venv) echo "04-python-venv" ;;
|
|
61
|
+
node-npm) echo "05-node-npm" ;;
|
|
62
|
+
keg-only) echo "06-keg-only" ;;
|
|
63
|
+
cask) echo "07-cask-only" ;;
|
|
64
|
+
go-binary) echo "08-go-binary" ;;
|
|
65
|
+
*) echo "invalid" ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Convert kebab-case or snake_case to CamelCase
|
|
70
|
+
# e.g. "my-test-cli" -> "MyTestCli", "my_test_cli" -> "MyTestCli"
|
|
71
|
+
to_camelcase() {
|
|
72
|
+
local input="$1"
|
|
73
|
+
# Replace underscores with hyphens for uniform splitting
|
|
74
|
+
input="${input//_/-}"
|
|
75
|
+
if [[ "$input" == *"-"* ]]; then
|
|
76
|
+
local result=""
|
|
77
|
+
local IFS='-'
|
|
78
|
+
read -ra parts <<< "$input"
|
|
79
|
+
for part in "${parts[@]}"; do
|
|
80
|
+
# Capitalize first letter, lowercase rest
|
|
81
|
+
local first="${part:0:1}"
|
|
82
|
+
local rest="${part:1}"
|
|
83
|
+
result+=""$(echo "$first" | tr '[:lower:]' '[:upper:]')$(echo "$rest" | tr '[:upper:]' '[:lower:]')""
|
|
84
|
+
done
|
|
85
|
+
echo "$result"
|
|
86
|
+
else
|
|
87
|
+
# No hyphens: just ensure first letter is capitalized
|
|
88
|
+
local first="${input:0:1}"
|
|
89
|
+
local rest="${input:1}"
|
|
90
|
+
echo "$(echo "$first" | tr '[:lower:]' '[:upper:]')$rest"
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Lowercase a name for filename
|
|
95
|
+
# e.g. "MyTestCli" -> "mytestcli", "my-test-cli" -> "my-test-cli"
|
|
96
|
+
to_lowercase_filename() {
|
|
97
|
+
local input="$1"
|
|
98
|
+
echo "$input" | tr '[:upper:]' '[:lower:]'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
PROJECT_TYPE=""
|
|
102
|
+
FORMULA_NAME=""
|
|
103
|
+
TAP_NAME=""
|
|
104
|
+
OUTPUT_DIR=""
|
|
105
|
+
WITH_CI=false
|
|
106
|
+
CONVERT_TO_BUN=false
|
|
107
|
+
|
|
108
|
+
# Parse arguments
|
|
109
|
+
while [[ $# -gt 0 ]]; do
|
|
110
|
+
case "$1" in
|
|
111
|
+
--help)
|
|
112
|
+
usage
|
|
113
|
+
exit 0
|
|
114
|
+
;;
|
|
115
|
+
--list)
|
|
116
|
+
list_templates
|
|
117
|
+
exit 0
|
|
118
|
+
;;
|
|
119
|
+
--name)
|
|
120
|
+
if [[ -z "${2:-}" ]]; then
|
|
121
|
+
echo "Error: --name requires an argument" >&2
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
|
124
|
+
FORMULA_NAME="$2"
|
|
125
|
+
shift 2
|
|
126
|
+
;;
|
|
127
|
+
--tap)
|
|
128
|
+
if [[ -z "${2:-}" ]]; then
|
|
129
|
+
echo "Error: --tap requires an argument" >&2
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
TAP_NAME="$2"
|
|
133
|
+
shift 2
|
|
134
|
+
;;
|
|
135
|
+
--output)
|
|
136
|
+
if [[ -z "${2:-}" ]]; then
|
|
137
|
+
echo "Error: --output requires an argument" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
OUTPUT_DIR="$2"
|
|
141
|
+
shift 2
|
|
142
|
+
;;
|
|
143
|
+
--with-ci)
|
|
144
|
+
WITH_CI=true
|
|
145
|
+
shift
|
|
146
|
+
;;
|
|
147
|
+
--convert-to-bun)
|
|
148
|
+
CONVERT_TO_BUN=true
|
|
149
|
+
shift
|
|
150
|
+
;;
|
|
151
|
+
--*)
|
|
152
|
+
echo "Error: unknown option $1" >&2
|
|
153
|
+
usage >&2
|
|
154
|
+
exit 1
|
|
155
|
+
;;
|
|
156
|
+
*)
|
|
157
|
+
if [[ -z "$PROJECT_TYPE" ]]; then
|
|
158
|
+
PROJECT_TYPE="$1"
|
|
159
|
+
shift
|
|
160
|
+
else
|
|
161
|
+
echo "Error: unexpected argument $1" >&2
|
|
162
|
+
usage >&2
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
;;
|
|
166
|
+
esac
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
if [[ -z "${PROJECT_TYPE:-}" ]]; then
|
|
170
|
+
echo "Error: project type is required" >&2
|
|
171
|
+
usage >&2
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if [[ -z "${OUTPUT_DIR:-}" ]]; then
|
|
176
|
+
echo "Error: --output is required" >&2
|
|
177
|
+
usage >&2
|
|
178
|
+
exit 1
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
TEMPLATE_DIR_NAME="$(map_type_to_dir "$PROJECT_TYPE")"
|
|
182
|
+
if [[ "$TEMPLATE_DIR_NAME" == "invalid" ]]; then
|
|
183
|
+
echo "Error: unknown project type '$PROJECT_TYPE'" >&2
|
|
184
|
+
echo "" >&2
|
|
185
|
+
list_templates >&2
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
TEMPLATE_SRC="$TEMPLATES_DIR/$TEMPLATE_DIR_NAME/tap-structure"
|
|
190
|
+
if [[ ! -d "$TEMPLATE_SRC" ]]; then
|
|
191
|
+
echo "Error: template source not found: $TEMPLATE_SRC" >&2
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Handle existing output directory
|
|
196
|
+
if [[ -e "$OUTPUT_DIR" ]]; then
|
|
197
|
+
if [[ -t 0 ]]; then
|
|
198
|
+
read -rp "Directory '$OUTPUT_DIR' already exists. Overwrite? [y/N] " answer
|
|
199
|
+
case "$answer" in
|
|
200
|
+
[Yy]*) rm -rf "$OUTPUT_DIR" ;;
|
|
201
|
+
*) echo "Aborted." >&2; exit 1 ;;
|
|
202
|
+
esac
|
|
203
|
+
else
|
|
204
|
+
echo "Error: output directory '$OUTPUT_DIR' already exists" >&2
|
|
205
|
+
exit 1
|
|
206
|
+
fi
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# Optionally run brew tap-new
|
|
210
|
+
if [[ "$WITH_CI" == true ]]; then
|
|
211
|
+
if [[ -z "$TAP_NAME" ]]; then
|
|
212
|
+
echo "Error: --tap is required when using --with-ci" >&2
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
brew tap-new "$TAP_NAME"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Copy template
|
|
219
|
+
mkdir -p "$OUTPUT_DIR"
|
|
220
|
+
cp -R "$TEMPLATE_SRC/"* "$OUTPUT_DIR/"
|
|
221
|
+
|
|
222
|
+
# Rename placeholder formula/cask files
|
|
223
|
+
if [[ -n "$FORMULA_NAME" ]]; then
|
|
224
|
+
CAMEL_NAME="$(to_camelcase "$FORMULA_NAME")"
|
|
225
|
+
LOWER_NAME="$(to_lowercase_filename "$FORMULA_NAME")"
|
|
226
|
+
|
|
227
|
+
# Find placeholder formula files
|
|
228
|
+
while IFS= read -r -d '' placeholder; do
|
|
229
|
+
dir="$(dirname "$placeholder")"
|
|
230
|
+
new_path="$dir/$LOWER_NAME.rb"
|
|
231
|
+
mv "$placeholder" "$new_path"
|
|
232
|
+
|
|
233
|
+
if [[ "$PROJECT_TYPE" == "cask" ]]; then
|
|
234
|
+
# Update cask token
|
|
235
|
+
sed -i.bak "s/cask \"<app>\"/cask \"$LOWER_NAME\"/g" "$new_path"
|
|
236
|
+
rm -f "$new_path.bak"
|
|
237
|
+
else
|
|
238
|
+
# Update formula class name
|
|
239
|
+
sed -i.bak "s/class <Formula>/class $CAMEL_NAME/g" "$new_path"
|
|
240
|
+
rm -f "$new_path.bak"
|
|
241
|
+
fi
|
|
242
|
+
done < <(find "$OUTPUT_DIR" \( -name '_formula.rb' -o -name '_app.rb' \) -print0)
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Handle --convert-to-bun for node-npm
|
|
246
|
+
if [[ "$CONVERT_TO_BUN" == true ]] && [[ "$PROJECT_TYPE" == "node-npm" ]]; then
|
|
247
|
+
FORMULA_FILE=$(find "$OUTPUT_DIR" -name '*.rb' | head -n 1)
|
|
248
|
+
if [[ -n "$FORMULA_FILE" ]]; then
|
|
249
|
+
{
|
|
250
|
+
echo "# NOTE: --convert-to-bun was specified. Uncomment the bun pattern below and remove the npm pattern."
|
|
251
|
+
cat "$FORMULA_FILE"
|
|
252
|
+
} > "$FORMULA_FILE.tmp"
|
|
253
|
+
mv "$FORMULA_FILE.tmp" "$FORMULA_FILE"
|
|
254
|
+
fi
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
echo "Tap structure created at: $OUTPUT_DIR"
|
|
258
|
+
if [[ -n "$TAP_NAME" ]]; then
|
|
259
|
+
echo "Tap: $TAP_NAME"
|
|
260
|
+
fi
|
|
261
|
+
if [[ -n "$FORMULA_NAME" ]]; then
|
|
262
|
+
echo "Formula: $FORMULA_NAME"
|
|
263
|
+
fi
|