• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sschmid / bee / 11244402405

08 Oct 2024 10:00PM UTC coverage: 91.617% (-0.1%) from 91.737%
11244402405

push

github

sschmid
Make install executable

765 of 835 relevant lines covered (91.62%)

190.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.96
/src/bee-run.bash
1
# shellcheck disable=SC1090,SC2153,SC2178
2

3
################################################################################
4
# defaults
5
################################################################################
6

7
: "${BEE_LATEST_VERSION_PATH:=https://raw.githubusercontent.com/sschmid/bee/main/version.txt}"
592✔
8
: "${BEE_WIKI:=https://github.com/sschmid/bee/wiki}"
592✔
9
: "${BEE_LATEST_VERSION_CACHE_EXPIRE:=14400}" # 4h * 60 * 60
592✔
10
: "${BEE_HUB_PULL_COOLDOWN:=900}" # 15m * 60
592✔
11

12
BEE_HUBS_CACHE_PATH="${BEE_CACHES_PATH}/hubs"
592✔
13
BEE_LINT_CACHE_PATH="${BEE_CACHES_PATH}/lint"
592✔
14
if [[ -v BEE_PLUGINS_PATHS ]]
592✔
15
then BEE_PLUGINS_PATHS+=("${BEE_CACHES_PATH}/plugins")
522✔
16
else BEE_PLUGINS_PATHS=("${BEE_CACHES_PATH}/plugins")
70✔
17
fi
18

19
################################################################################
20
# help
21
################################################################################
22

23
bee::help() {
24
  cat << EOF
56✔
25

26
██████╗ ███████╗███████╗
27
██╔══██╗██╔════╝██╔════╝
28
██████╔╝█████╗  █████╗
29
██╔══██╗██╔══╝  ██╔══╝
30
██████╔╝███████╗███████╗
31
╚═════╝ ╚══════╝╚══════╝
32

33
${BEE_ICON} bee $(bee::version) - plugin-based bash automation
34

35
usage: bee [--help]
36
           [--quiet] [--verbose]
37
           [--batch] <command> [<args>]
38

39
  cache [--clear [<path>]]                open (or --clear) cache
40
  env <vars>                              print env variables
41
  hash <path>                             generate plugin hash
42
  hubs [--all | --list ] [<urls>]         list hubs and their plugins (--all versions as --list)
43
  info <plugin>                           print plugin spec
44
  install [--force] [<plugins>]           install plugins (--force ignore sha256 mismatch)
45
  job [--time] [--logfile]
46
      <title> <command>                   run command as a job (show elapsed --time)
47
                                          (write output to --logfile in bee resources directory)
48
  lint <spec>                             validate plugin spec
49
  new [<path>]                            create new Beefile
50
  plugins [--all | --lock | --outdated]
51
          [--version]                     list (--all or --outdated) plugins (with --version)
52
  pull [--force] [<urls>]                 update hubs (--force ignore pull cooldown)
53
  res <plugins>                           copy plugin resources into bee resources directory
54
  update                                  update bee to the latest version
55
  version [--latest] [--cached]           print (--latest) version (--cached locally)
56
  wiki                                    open wiki
57

58
EOF
59
}
60

61
################################################################################
62
# cache
63
################################################################################
64

65
bee::cache::comp() {
66
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
6✔
67
    echo --clear
2✔
68
  elif (( $# == 1 || $# == 2 && COMP_PARTIAL )); then
4✔
69
    [[ ! -d "${BEE_CACHES_PATH}" ]] || ls "${BEE_CACHES_PATH}"
4✔
70
  fi
71
}
72

73
bee::cache() {
74
  if (( $# )); then
10✔
75
    case "$1" in
8✔
76
      --clear) rm -rf "${BEE_CACHES_PATH}${2:+/$2}" ;;
6✔
77
      *) bee::help ;;
2✔
78
    esac
79
  else
80
    os_open "${BEE_CACHES_PATH}"
2✔
81
  fi
82
}
83

84
################################################################################
85
# hash
86
################################################################################
87

88
BEE_HUB_HASH_RESULT=""
592✔
89

90
bee::hash() {
91
  if (( ! $# )); then
150✔
92
    bee::help
2✔
93
  else
94
    local exclude=(^./.git .DS_Store$)
296✔
95
    # shellcheck disable=SC2207
96
    [[ -v BEE_HUB_HASH_EXCLUDE ]] && exclude+=($(bee::split_args "${BEE_HUB_HASH_EXCLUDE:-}"))
152✔
97
    local path="$1" file_hash all
148✔
98
    local -a hashes=()
296✔
99
    echo "${path}"
148✔
100
    pushd "${path}" >/dev/null || exit 1
148✔
101
      local file
148✔
102
      local -i ignore=0
148✔
103
      while read -r file; do
3,934✔
104
        ignore=0
3,786✔
105
        for pattern in "${exclude[@]}"; do
4,210✔
106
          if [[ "${file}" =~ ${pattern} ]]; then
4,210✔
107
            ignore=1
3,380✔
108
            break
3,380✔
109
          fi
110
        done
111
        if (( ! ignore )); then
3,786✔
112
          file_hash="$(os_sha256sum "${file}")"
812✔
113
          echo "${file_hash}"
406✔
114
          hashes+=("${file_hash// */}")
406✔
115
        fi
116
      done < <(find . -type f | LC_ALL=C sort)
117
    popd >/dev/null || exit 1
148✔
118
    all="$(echo "${hashes[*]}" | LC_ALL=C sort | os_sha256sum)"
740✔
119
    echo "${all}"
148✔
120
    BEE_HUB_HASH_RESULT="${all// */}"
148✔
121
  fi
122
}
123

124
################################################################################
125
# hubs
126
################################################################################
127

128
bee::hubs::comp() {
129
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
8✔
130
    local cmd="${1:-}" comps=(--all --list "${BEE_HUBS[*]}")
4✔
131
    compgen -W "${comps[*]}" -- "${cmd}"
2✔
132
  else
133
    echo "${BEE_HUBS[*]}"
6✔
134
  fi
135
}
136

137
bee::hubs() {
138
  local -i show_all=0 list=0
18✔
139
  while (( $# )); do
30✔
140
    case "$1" in
14✔
141
      --all) show_all=1; shift ;;
4✔
142
      --list) list=1; shift ;;
20✔
143
      --) shift; break ;; *) break ;;
2✔
144
    esac
145
  done
146

147
  if (( list )); then
18✔
148
    local cache_path
10✔
149
    for url in "${@:-"${BEE_HUBS[@]}"}"; do
20✔
150
      cache_path="$(bee::to_cache_path "${url}")"
40✔
151
      if [[ -n "${cache_path}" ]]; then
20✔
152
        cache_path="${BEE_HUBS_CACHE_PATH}/${cache_path}"
20✔
153
        [[ ! -d "${cache_path}" ]] || ls "${cache_path}"
32✔
154
      fi
155
    done
156
  else
157
    local cache_path plugin_name plugin_version indent bullet
8✔
158
    local -a plugins versions
8✔
159
    local -i i j n m
8✔
160
    for url in "${@:-"${BEE_HUBS[@]}"}"; do
14✔
161
      cache_path="$(bee::to_cache_path "${url}")"
28✔
162
      if [[ -n "${cache_path}" ]]; then
14✔
163
        cache_path="${BEE_HUBS_CACHE_PATH}/${cache_path}"
14✔
164
        echo "${url}"
14✔
165
        if [[ -d "${cache_path}" ]]; then
14✔
166
          mapfile -t plugins < <(ls "${cache_path}")
20✔
167
          n=${#plugins[@]}
10✔
168
          for (( i = 0; i < n; i++ )); do
×
169
            plugin_name="${plugins[i]}"
50✔
170
            (( i == n - 1 )) && bullet="└── " || bullet="├── "
100✔
171
            echo "${bullet}${plugin_name}"
50✔
172

173
            if (( show_all )); then
50✔
174
              mapfile -t versions < <(find "${cache_path}/${plugin_name}" -mindepth 1 -maxdepth 1 -type d | LC_ALL=C sort -V)
80✔
175
              m=${#versions[@]}
20✔
176
              for (( j = 0; j < m; j++ )); do
224✔
177
                plugin_version="$(basename "${versions[j]}")"
64✔
178
                (( i == n - 1 )) && indent="    " || indent="│    "
64✔
179
                (( j == m - 1 )) && bullet="└── " || bullet="├── "
64✔
180
                echo "${indent}${bullet}${plugin_version}"
32✔
181
              done
182
            fi
183
          done
184
          echo
10✔
185
        fi
186
      fi
187
    done
188
  fi
189
}
190

191
bee::to_cache_path() {
192
  case "$1" in
468✔
193
    https://*) echo "$(dirname "${1#https://}")/$(basename "$1" .git)" ;;
6✔
194
    git://*) echo "$(dirname "${1#git://}")/$(basename "$1" .git)" ;;
6✔
195
    git@*) local path="${1#git@}"; echo "$(dirname "${path/://}")/$(basename "$1" .git)" ;;
8✔
196
    ssh://*) local path="${1#ssh://}"; echo "$(dirname "${path#git@}")/$(basename "$1" .git)" ;;
8✔
197
    file://*) basename "$1" ;;
446✔
198
    *) bee::log_warning "Unsupported url: $1" ;;
6✔
199
  esac
200
}
201

202
################################################################################
203
# info
204
################################################################################
205

206
bee::info::comp() {
207
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
6✔
208
    {
209
      bee::hubs --list
4✔
210
      bee::comp_plugins
4✔
211
    } | awk '!line[$0]++'
4✔
212
  fi
213
}
214

215
bee::info() {
216
  if (( ! $# )); then
10✔
217
    bee::help
2✔
218
  else
219
    local plugin="$1" plugin_name plugin_version cache_path spec_path is_local
8✔
220
    for url in "${BEE_HUBS[@]}"; do
24✔
221
      cache_path="${BEE_HUBS_CACHE_PATH}/$(bee::to_cache_path "${url}")"
24✔
222
      while read -r plugin_name plugin_version spec_path is_local; do
12✔
223
        spec_path="${spec_path}/${plugin_version}/plugin.json"
4✔
224
        echo "${spec_path}"
4✔
225
        jq . "${spec_path}" || cat "${spec_path}"
6✔
226
        return
4✔
227
      done < <(bee::resolve "${plugin}" "${cache_path}" "plugin.json")
228
    done
229
    for path in "${BEE_PLUGINS_PATHS[@]}"; do
16✔
230
      while read -r plugin_name plugin_version spec_path is_local; do
8✔
231
        if (( is_local ))
4✔
232
        then spec_path="${spec_path}/plugin.json"
2✔
233
        else spec_path="${spec_path}/${plugin_version}/plugin.json"
2✔
234
        fi
235
        echo "${spec_path}"
4✔
236
        jq . "${spec_path}" || cat "${spec_path}"
4✔
237
        return
4✔
238
      done < <(bee::resolve "${plugin}" "${path}" "plugin.json" 1)
239
    done
240
  fi
241
}
242

243
################################################################################
244
# install
245
################################################################################
246

247
bee::install::comp() {
248
  local plugins
4✔
249
  plugins="$(bee::hubs --list)"
8✔
250
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
4✔
251
    local cmd="${1:-}" comps=(--force "${plugins}")
4✔
252
    compgen -W "${comps[*]}" -- "${cmd}"
2✔
253
  else
254
    echo "${plugins}"
2✔
255
  fi
256
}
257

258
declare -Ag BEE_INSTALL_HASHES=()
1,178✔
259

260
bee::install() {
261
  BEE_INSTALL_HASHES=()
262
  bee::pull
56✔
263
  local -i force=0
56✔
264
  while (( $# )); do
58✔
265
    case "$1" in
40✔
266
      --force) force=1; shift ;;
4✔
267
      --) shift; break ;; *) break ;;
38✔
268
    esac
269
  done
270
  if (( $# )); then
56✔
271
    echo "Installing"
38✔
272
    bee::install::recursively ${force} 0 "" "$@"
38✔
273
  elif [[ -v BEE_FILE ]]; then
18✔
274
    if [[ -f "${BEE_FILE}.lock" ]]; then
18✔
275
      echo "Installing plugins based on ${BEE_FILE}.lock"
6✔
276
      mapfile -t plugins < <(awk '/^├── / {print $2}; /^└── / {print $2}' "${BEE_FILE}.lock")
12✔
277
      mapfile -t plugins < <(awk '!line[$0]++' <<< "${plugins[*]}")
12✔
278
      bee::install::recursively ${force} 0 "" "${plugins[@]}"
6✔
279
    else
280
      echo "Installing plugins based on ${BEE_FILE}"
12✔
281
      bee::install::recursively ${force} 1 "" "${BEE_PLUGINS[@]}"
12✔
282
    fi
283
  else
284
    echo "No Beefile"
×
285
  fi
286
}
287

288
bee::install::recursively() {
289
  local -i force="$1" lock="$2"
100✔
290
  local indent="$3"
100✔
291
  shift 3
100✔
292
  local -a plugins=("$@") missing=()
300✔
293
  local plugin plugin_name plugin_version cache_path spec_path is_local bullet
100✔
294
  local -i i n=${#plugins[@]} found=0 already_installed=0
100✔
295
  for (( i = 0; i < n; i++ )); do
512✔
296
    found=0
158✔
297
    plugin="${plugins[i]// /}"
158✔
298
    (( i == n - 1 )) && bullet="└── " || bullet="├── "
316✔
299
    for url in "${BEE_HUBS[@]}"; do
496✔
300
      cache_path="${BEE_HUBS_CACHE_PATH}/$(bee::to_cache_path "${url}")"
356✔
301
      while read -r plugin_name plugin_version spec_path is_local; do
316✔
302
        found=1
140✔
303
        spec_path="${spec_path}/${plugin_version}/plugin.json"
140✔
304
        local plugin_path="${BEE_CACHES_PATH}/plugins/${plugin_name}/${plugin_version}"
140✔
305
        local git tag sha deps
140✔
306
        while read -r git tag sha deps; do
278✔
307
          (( lock )) && echo "${indent}${bullet}${plugin_name}:${plugin_version}" >> "${BEE_FILE}.lock"
180✔
308
          if [[ -d "${plugin_path}" ]]; then
140✔
309
            already_installed=1
34✔
310
          else
311
            already_installed=0
106✔
312
            git -c advice.detachedHead=false clone -q --depth 1 --branch "${tag}" "${git}" "${plugin_path}" || true
106✔
313
          fi
314
          if [[ -d "${plugin_path}" ]]; then
140✔
315
            if [[ -v BEE_INSTALL_HASHES["${plugin_path}"] ]]; then
140✔
316
              BEE_HUB_HASH_RESULT="${BEE_INSTALL_HASHES["${plugin_path}"]}"
32✔
317
            else
318
              bee::hash "${plugin_path}" >/dev/null
108✔
319
              BEE_INSTALL_HASHES["${plugin_path}"]="${BEE_HUB_HASH_RESULT}"
108✔
320
            fi
321
            if [[ "${BEE_HUB_HASH_RESULT}" != "${sha}" ]]; then
140✔
322
              if (( force )); then
8✔
323
                bee::log_warning "${plugin_name}:${plugin_version} sha256 mismatch!" \
2✔
324
                  "Plugin was tampered with or version has been modified. Authenticity is not guaranteed." \
325
                  "Consider deleting ${plugin_path} and run 'bee install ${plugin_name}:${plugin_version}'."
326
                echo -e "${indent}${bullet}${BEE_COLOR_WARN}${BEE_CHECK_SUCCESS} ${plugin_name}:${plugin_version} (${url})${BEE_COLOR_RESET}"
2✔
327
              else
328
                bee::log_error "${plugin_name}:${plugin_version} sha256 mismatch!" "Deleting ${plugin_path}" \
6✔
329
                  "Use 'bee info ${plugin_name}:${plugin_version}' to inspect the plugin definition." \
330
                  "Use 'bee install --force ${plugin_name}:${plugin_version}' to install anyway and proceed at your own risk."
331
                rm -rf "${plugin_path}"
6✔
332
                echo -e "${indent}${bullet}${BEE_COLOR_FAIL}${BEE_CHECK_FAIL} ${plugin_name}:${plugin_version} (${url})${BEE_COLOR_RESET}"
6✔
333
              fi
334
            else
335
              if (( already_installed )); then
132✔
336
                echo -e "${indent}${bullet}${plugin_name}:${plugin_version} (${url})"
34✔
337
              else
338
                echo -e "${indent}${bullet}${BEE_COLOR_SUCCESS}${BEE_CHECK_SUCCESS} ${plugin_name}:${plugin_version} (${url})${BEE_COLOR_RESET}"
98✔
339
              fi
340
            fi
341
          else
342
            echo -e "${indent}${bullet}${BEE_COLOR_FAIL}${BEE_CHECK_FAIL} ${plugin_name}:${plugin_version} (${url})${BEE_COLOR_RESET}"
×
343
          fi
344
          # shellcheck disable=SC2086
345
          if [[ -n "${deps}" ]]; then
140✔
346
            if (( i == n - 1 ))
34✔
347
            then bee::install::recursively ${force} ${lock} "${indent}    " ${deps}
10✔
348
            else bee::install::recursively ${force} ${lock} "${indent}│   " ${deps}
24✔
349
            fi
350
          fi
351
        done < <(jq -r '[.git, .tag, .sha256, .dependencies[]?] | @tsv' "${spec_path}")
352
      done < <(bee::resolve "${plugin}" "${cache_path}" "plugin.json")
353
      (( found )) && break
314✔
354
    done
355
    if (( ! found )); then
156✔
356
      bee::load_plugin "${plugin}" 1
18✔
357
      if [[ -n "${BEE_LOAD_PLUGIN_NAME}" ]]; then
18✔
358
        (( lock )) && echo "${indent}${bullet}${BEE_LOAD_PLUGIN_NAME}:local" >> "${BEE_FILE}.lock"
14✔
359
        echo -e "${indent}${bullet}${BEE_LOAD_PLUGIN_NAME}:local (${BEE_LOAD_PLUGIN_PATH})"
10✔
360
        if [[ -f "${BEE_LOAD_PLUGIN_JSON_PATH}" ]]; then
10✔
361
          # shellcheck disable=SC2046,SC2086
362
          if (( i == n - 1 ))
10✔
363
          then bee::install::recursively ${force} ${lock} "${indent}    " $(jq -r '.dependencies[]?' "${BEE_LOAD_PLUGIN_JSON_PATH}")
20✔
364
          else bee::install::recursively ${force} ${lock} "${indent}│   " $(jq -r '.dependencies[]?' "${BEE_LOAD_PLUGIN_JSON_PATH}")
×
365
          fi
366
        fi
367
      else
368
        missing+=("${plugin}")
8✔
369
        echo -e "${indent}${bullet}${BEE_COLOR_FAIL}${BEE_CHECK_FAIL} ${plugin}${BEE_COLOR_RESET}"
8✔
370
      fi
371
    fi
372
  done
373
  if (( ${#missing[@]} )); then
98✔
374
    for m in "${missing[@]}"; do
8✔
375
      bee::log_error "Couldn't install plugin: ${m}"
8✔
376
    done
377
    return 1
6✔
378
  fi
379
}
380

381
################################################################################
382
# job
383
################################################################################
384

385
BEE_JOB_SPINNER_INTERVAL=0.1
592✔
386
BEE_JOB_SPINNER_FRAMES=('🐝' ' 🐝' '  🐝' '   🐝' '    🐝' '     🐝' '      🐝' '       🐝' '        🐝' '         🐝' '        🐝' '       🐝' '      🐝' '     🐝' '    🐝' '   🐝' '  🐝' ' 🐝' '🐝')
592✔
387
declare -ig BEE_JOB_SPINNER_PID=0
592✔
388
declare -ig BEE_JOB_RUNNING=0
592✔
389
declare -ig BEE_JOB_T=0
592✔
390
declare -ig BEE_JOB_LOG_TO_FILE=0
592✔
391
declare -ig BEE_JOB_SHOW_TIME=0
592✔
392
BEE_JOB_TITLE=""
592✔
393
BEE_JOB_LOGFILE=""
592✔
394

395
bee::job::comp() {
396
  local comps=(--logfile --time)
16✔
397
  while (( $# )); do
16✔
398
    case "$1" in
8✔
399
      --logfile) comps=("${comps[@]/--logfile/}"); shift ;;
8✔
400
      --time) comps=("${comps[@]/--time/}"); shift ;;
8✔
401
      --) shift; break ;; *) break ;;
×
402
    esac
403
  done
404
  compgen -W "${comps[*]}" -- "${1:-}"
8✔
405
}
406

407
bee::job() {
408
  if (( $# >= 2 )); then
100✔
409
    while (( $# )); do
110✔
410
      case "$1" in
110✔
411
        --logfile) BEE_JOB_LOG_TO_FILE=1; shift ;;
24✔
412
        --time) BEE_JOB_SHOW_TIME=1; shift ;;
4✔
413
        --) shift; break ;; *) break ;;
96✔
414
      esac
415
    done
416

417
    bee::job::start "$@"
96✔
418
    bee::job::finish
82✔
419
  else
420
    bee::help
4✔
421
  fi
422
}
423

424
bee::job::start() {
425
  BEE_JOB_TITLE="$1"; shift
192✔
426
  if (( BEE_JOB_LOG_TO_FILE )); then
96✔
427
    mkdir -p "${BEE_RESOURCES}/logs"
12✔
428
    BEE_JOB_LOGFILE="${BEE_RESOURCES}/logs/$(date -u '+%Y%m%d%H%M%S')-job-${BEE_JOB_TITLE// /-}-${RANDOM}${RANDOM}.log"
24✔
429
  else
430
    BEE_JOB_LOGFILE=/dev/null
84✔
431
  fi
432

433
  if (( BEE_VERBOSE )); then
96✔
434
    echo "${BEE_JOB_TITLE}"
4✔
435
    bee::run "$@" 2>&1 | tee "${BEE_JOB_LOGFILE}"
8✔
436
  else
437
    bee::job::start_spinner
92✔
438
    bee::run "$@" &> "${BEE_JOB_LOGFILE}"
92✔
439
  fi
440
}
441

442
bee::job::finish() {
443
  bee::job::stop_spinner
82✔
444
  local line_reset
82✔
445
  (( ! BEE_VERBOSE )) && line_reset="${BEE_LINE_RESET}" || line_reset=""
164✔
446
  echo -e "${line_reset}${BEE_COLOR_SUCCESS}${BEE_JOB_TITLE} ${BEE_CHECK_SUCCESS}$(bee::job::duration)${BEE_COLOR_RESET}"
164✔
447
}
448

449
bee::job::start_spinner() {
450
  BEE_JOB_RUNNING=1
92✔
451
  BEE_JOB_T=${SECONDS}
92✔
452
  bee::add_int_trap bee::job::INT
92✔
453
  bee::add_exit_trap bee::job::EXIT
92✔
454
  if [[ -t 1 ]]; then
92✔
455
    tput civis &>/dev/null || true
×
456
    stty -echo
×
457
    bee::job::spin &
×
458
    BEE_JOB_SPINNER_PID=$!
×
459
  fi
460
}
461

462
bee::job::stop_spinner() {
463
  bee::remove_int_trap bee::job::INT
94✔
464
  bee::remove_exit_trap bee::job::EXIT
94✔
465
  if [[ -t 1 ]]; then
94✔
466
    if (( BEE_JOB_SPINNER_PID != 0 )); then
×
467
      kill -9 ${BEE_JOB_SPINNER_PID} || true
×
468
      wait ${BEE_JOB_SPINNER_PID} &>/dev/null || true
×
469
      BEE_JOB_SPINNER_PID=0
×
470
    fi
471
    stty echo
×
472
    tput cnorm &>/dev/null || true
×
473
  fi
474
  BEE_JOB_RUNNING=0
94✔
475
}
476

477
bee::job::spin() {
478
  while true; do
×
479
    for i in "${BEE_JOB_SPINNER_FRAMES[@]}"; do
×
480
      echo -ne "${BEE_LINE_RESET}${BEE_JOB_TITLE}$(bee::job::duration) ${i}"
×
481
      sleep ${BEE_JOB_SPINNER_INTERVAL}
×
482
    done
483
  done
484
}
485

486
bee::job::INT() {
487
  (( BEE_JOB_RUNNING )) || return 0
×
488
  bee::job::stop_spinner
×
489
  echo "Aborted by $(whoami)$(bee::job::duration)" >> "${BEE_JOB_LOGFILE}"
×
490
}
491

492
bee::job::EXIT() {
493
  local -i status=$1
12✔
494
  (( BEE_JOB_RUNNING )) || return 0
12✔
495
  if (( status )); then
12✔
496
    bee::job::stop_spinner
12✔
497
    echo -e "${BEE_LINE_RESET}${BEE_COLOR_FAIL}${BEE_JOB_TITLE} ${BEE_CHECK_FAIL}$(bee::job::duration)${BEE_COLOR_RESET}"
24✔
498
  fi
499
}
500

501
bee::job::duration() {
502
  (( ! BEE_JOB_SHOW_TIME )) || echo " ($(( SECONDS - BEE_JOB_T )) seconds)"
96✔
503
}
504

505
################################################################################
506
# lint
507
################################################################################
508

509
bee::lint() {
510
  if (( ! $# )); then
44✔
511
    bee::help
2✔
512
  else
513
    local spec_path="$1" key actual expected cache_path plugin_name git_url git_tag sha256_hash
42✔
514
    local -a plugin_deps
42✔
515

516
    key="name"
42✔
517
    plugin_name="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
518
    expected="$(basename "$(dirname "$(dirname "${spec_path}")")")"
168✔
519
    bee::lint::assert_equal "${key}" "${plugin_name}" "${expected}"
42✔
520

521
    key="version"
42✔
522
    actual="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
523
    expected="$(basename "$(dirname "${spec_path}")")"
126✔
524
    bee::lint::assert_equal "${key}" "${actual}" "${expected}"
42✔
525

526
    key="license"
42✔
527
    actual="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
528
    bee::lint::assert_exist "${key}" "${actual}"
42✔
529

530
    key="homepage"
42✔
531
    actual="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
532
    bee::lint::assert_exist "${key}" "${actual}"
42✔
533

534
    key="authors"
42✔
535
    actual="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
536
    bee::lint::assert_exist "${key}" "${actual}"
42✔
537

538
    key="info"
42✔
539
    actual="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
540
    bee::lint::assert_exist "${key}" "${actual}"
42✔
541

542
    key="git"
42✔
543
    git_url="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
544
    bee::lint::assert_exist "${key}" "${git_url}"
42✔
545

546
    key="tag"
42✔
547
    git_tag="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
548
    bee::lint::assert_exist "${key}" "${git_tag}"
42✔
549

550
    key="sha256"
42✔
551
    sha256_hash="$(jq -rc --arg key "${key}" '.[$key]' "${spec_path}")"
84✔
552
    bee::lint::assert_exist "${key}" "${sha256_hash}"
42✔
553

554
    key="dependencies"
42✔
555
    plugin_deps=("$(jq -rc --arg key "${key}" '.[$key][]? // null' "${spec_path}")")
84✔
556
    bee::lint::optional "${key}" "${plugin_deps[@]}"
42✔
557

558
    cache_path="$(bee::to_cache_path "${git_url}")"
84✔
559
    if [[ -n "${cache_path}" ]]; then
42✔
560
      cache_path="${BEE_LINT_CACHE_PATH}/${cache_path}"
40✔
561
      if [[ -d "${cache_path}" ]]; then
40✔
562
        pushd "${cache_path}" >/dev/null || exit 1
×
563
          bee::job "git fetch" git fetch
×
564
        popd >/dev/null || exit 1
×
565
      else
566
        bee::job "git clone" git clone "${git_url}" "${cache_path}"
40✔
567
      fi
568
    fi
569

570
    if [[ -n "${cache_path}" && -d "${cache_path}" ]]; then
78✔
571
      pushd "${cache_path}" >/dev/null || exit 1
38✔
572
        bee::job "git checkout tag" git checkout -q "${git_tag}"
38✔
573

574
        key="version file"
34✔
575
        local version_file="version.txt"
34✔
576
        if [[ -f "${version_file}" ]]; then
34✔
577
          actual="$(cat "${version_file}")"
64✔
578
          expected="$(basename "$(dirname "${spec_path}")")"
96✔
579
          bee::lint::assert_equal "${key}" "${actual}" "${expected}"
32✔
580
        else
581
          version_file="null"
2✔
582
          bee::lint::assert_exist "${key}" "${version_file}"
2✔
583
        fi
584

585
        key="license file"
34✔
586
        local license_file="LICENSE.txt"
34✔
587
        [[ -f "${license_file}" ]] || license_file="null"
38✔
588
        bee::lint::assert_exist "${key}" "${license_file}"
34✔
589

590
        key="sha256"
34✔
591
        bee::hash "${PWD}"
34✔
592
        bee::lint::assert_equal "${key}" "${sha256_hash}" "${BEE_HUB_HASH_RESULT}"
34✔
593

594
        key="plugin file"
34✔
595
        local plugin_file="${plugin_name}.bash"
34✔
596
        [[ -f "${plugin_file}" ]] || plugin_file="null"
40✔
597
        bee::lint::assert_exist "${key}" "${plugin_file}"
34✔
598

599
        key="dependencies"
34✔
600
        local deps
34✔
601
        if [[ -f plugin.json ]]
34✔
602
        then deps="$(jq -r '.dependencies[]? // null' plugin.json)"
4✔
603
        else deps="null"
32✔
604
        fi
605
        bee::lint::assert_equal "${key}" \
170✔
606
          "$(echo "${plugin_deps[@]}" | tr '\n' ' ')" \
607
          "$(echo "${deps}" | tr '\n' ' ')"
608
      popd >/dev/null || exit 1
34✔
609
    fi
610

611
    (( ! BEE_HUB_LINT_ERROR )) || return 1
68✔
612
  fi
613
}
614

615
declare -ig BEE_HUB_LINT_ERROR=0
592✔
616

617
bee::lint::assert_equal() {
618
  local key="$1" actual="$2" expected="$3"
184✔
619
  if [[ "${actual}" == "${expected}" ]]; then
184✔
620
    printf '%-24b%b\n' "${BEE_COLOR_SUCCESS}${key}" "${BEE_CHECK_SUCCESS} ${actual}${BEE_COLOR_RESET}"
142✔
621
  else
622
    printf '%-24b%b\n' "${BEE_COLOR_FAIL}${key}" "${BEE_CHECK_FAIL} ${actual} (must be ${expected})${BEE_COLOR_RESET}"
42✔
623
    BEE_HUB_LINT_ERROR=1
42✔
624
  fi
625
}
626

627
bee::lint::assert_exist() {
628
  local key="$1" actual="$2"
364✔
629
  if [[ "${actual}" != "null" ]]; then
364✔
630
    printf '%-24b%b\n' "${BEE_COLOR_SUCCESS}${key}" "${BEE_CHECK_SUCCESS} ${actual}${BEE_COLOR_RESET}"
338✔
631
  else
632
    printf '%-24b%b\n' "${BEE_COLOR_FAIL}${key}" "${BEE_CHECK_FAIL} ${actual} (required)${BEE_COLOR_RESET}"
26✔
633
    BEE_HUB_LINT_ERROR=1
26✔
634
  fi
635
}
636

637
bee::lint::optional() {
638
  local key="$1" actual="$2"
42✔
639
  if [[ "${actual}" != "null" ]]
42✔
640
  then printf '%-24b%b\n' "${BEE_COLOR_SUCCESS}${key}" "${BEE_CHECK_SUCCESS} ${actual}${BEE_COLOR_RESET}"
2✔
641
  else printf '%-24b%b\n' "${BEE_COLOR_WARN}${key}" "${actual}${BEE_COLOR_RESET}"
40✔
642
  fi
643
}
644

645
################################################################################
646
# new
647
################################################################################
648

649
bee::new() {
650
  local beefile="${1:-Beefile}"
2✔
651
  if [[ -f "${beefile}" ]]; then
2✔
652
    bee::log_error "${beefile} already exists"
×
653
    return 1
×
654
  else
655
    cat << EOF > "${beefile}"
6✔
656
BEE_PROJECT="$(basename "${PWD}")"
657
BEE_VERSION=$(bee::version)
658

659
# Which plugins would you like to load?
660
# Standard plugins can be found in the official bee plugin register: https://github.com/sschmid/beehub
661
# More registers (and private registers) can be added by customizing ~/.beerc
662
#   BEE_HUBS=(
663
#     https://github.com/sschmid/beehub.git
664
#     https://github.com/my/beehub.git
665
#   )
666
#
667
# Custom local plugins may be added by customizing ~/.beerc
668
#   BEE_PLUGINS_PATHS=("${HOME}/path/to/my/plugins")
669
#
670
# Example format: BEE_PLUGINS=(changelog github:2.0.0 semver slack:1.0.0)
671
# You can specify a plugin version like this: plugin:x.y.z,
672
# otherwise the latest plugin version will be used
673
BEE_PLUGINS=(
674
  # android
675
  # changelog
676
  # github
677
  # ios
678
  # macos
679
  # sample
680
  # semver
681
  # slack
682
  # tree
683
  # unity
684
)
685
EOF
686
    bee::log_echo "Created ${beefile}"
2✔
687
  fi
688
}
689

690
################################################################################
691
# plugins
692
################################################################################
693

694
bee::plugins::comp() {
695
  local comps=(--all --lock --outdated --version)
20✔
696
  while (( $# )); do
18✔
697
    case "$1" in
8✔
698
      --all) comps=("${comps[@]/--all/}"); shift ;;
4✔
699
      --lock) comps=("${comps[@]/--lock/}"); shift ;;
4✔
700
      --outdated) comps=("${comps[@]/--outdated/}"); shift ;;
4✔
701
      --version) comps=("${comps[@]/--version/}"); shift ;;
4✔
702
      --) shift; break ;; *) break ;;
×
703
    esac
704
  done
705
  compgen -W "${comps[*]}" -- "${1:-}"
10✔
706
}
707

708
bee::plugins() {
709
  local -i show_all=0
32✔
710
  local -i show_lock=0
32✔
711
  local -i show_outdated=0
32✔
712
  local -i show_version=0
32✔
713
  while (( $# )); do
50✔
714
    case "$1" in
20✔
715
      --all) show_all=1; shift ;;
4✔
716
      --lock) show_lock=1; shift ;;
12✔
717
      --outdated) show_outdated=1; shift ;;
4✔
718
      --version) show_version=1; shift ;;
16✔
719
      --) shift; break ;; *) break ;;
2✔
720
    esac
721
  done
722

723
  if (( $# )); then
32✔
724
    bee::help
2✔
725
  else
726
    local plugin_entry plugin_version
30✔
727
    local -a plugins found=() missing=()
90✔
728
    if (( show_all )); then
30✔
729
      mapfile -t plugins < <(bee::comp_plugins)
4✔
730
      plugins=("${BEE_PLUGINS[@]}" "${plugins[@]}")
2✔
731
    else
732
      plugins=("${BEE_PLUGINS[@]}")
28✔
733
    fi
734
    if (( show_lock )); then
30✔
735
      [[ -v BEE_FILE && -f "${BEE_FILE}.lock" ]] || return 1
14✔
736
      mapfile -t plugins < <(tr -d '└├│─' < "${BEE_FILE}.lock")
8✔
737
      mapfile -t plugins < <(echo "${plugins[*]// /}" | awk '!line[$0]++')
12✔
738
    fi
739
    for plugin in "${plugins[@]}"; do
64✔
740
      bee::mapped_plugin "${plugin}"
64✔
741
      if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
64✔
742
        plugin_entry="${BEE_RESOLVE_PLUGIN_NAME}"
50✔
743
        plugin_version="${BEE_RESOLVE_PLUGIN_VERSION}"
50✔
744
        (( show_version || show_lock || show_outdated )) && plugin_entry="${plugin_entry}:${plugin_version}"
70✔
745
        if (( show_lock )); then
50✔
746
          if [[ -z "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
6✔
747
            missing+=("${BEE_COLOR_FAIL}${BEE_CHECK_FAIL} ${plugin_entry}${BEE_COLOR_RESET}")
×
748
          fi
749
        elif (( show_outdated )); then
44✔
750
          bee::resolve_plugin "${BEE_RESOLVE_PLUGIN_NAME}"
4✔
751
          if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" && "${BEE_RESOLVE_PLUGIN_VERSION}" != "${plugin_version}" ]]; then
8✔
752
            found+=("${plugin_entry} ${BEE_RESULT} ${BEE_RESOLVE_PLUGIN_NAME}:${BEE_RESOLVE_PLUGIN_VERSION}")
2✔
753
          fi
754
        else
755
          found+=("${plugin_entry}")
40✔
756
        fi
757
      else
758
        missing+=("${BEE_COLOR_FAIL}${BEE_CHECK_FAIL} ${plugin}${BEE_COLOR_RESET}")
14✔
759
      fi
760
    done
761

762
    (( ${#found[@]} )) && echo "${found[*]}" | LC_ALL=C sort -u
82✔
763
    if (( ${#missing[@]} )); then
28✔
764
      echo -e "${missing[*]}" | LC_ALL=C sort -u
24✔
765
      return 1
8✔
766
    fi
767
  fi
768
}
769

770
bee::resolve() {
771
  local plugin="$1" plugins_path="$2" file="$3"
1,192✔
772
  local -i allow_local=${4:-0}
1,192✔
773
  local plugin_name="${plugin%:*}" plugin_version="${plugin##*:}" path
1,192✔
774
  path="${plugins_path}/${plugin_name}"
1,192✔
775
  if [[ ${allow_local} -eq 1 && -f "${path}/${file}" ]]; then
2,194✔
776
    echo -e "${plugin_name}\tlocal\t${path}\t1"
30✔
777
  else
778
    if [[ "${plugin_name}" == "${plugin_version}" && -d "${path}" ]]; then
1,652✔
779
      plugin_version="$(basename "$(find "${path}" -mindepth 1 -maxdepth 1 -type d | LC_ALL=C sort -rV | head -n 1)")"
804✔
780
    fi
781
    [[ ! -f "${path}/${plugin_version}/${file}" ]] || echo -e "${plugin_name}\t${plugin_version}\t${path}\t0"
1,582✔
782
  fi
783
}
784

785
BEE_RESOLVE_PLUGIN_NAME=""
592✔
786
BEE_RESOLVE_PLUGIN_VERSION=""
592✔
787
BEE_RESOLVE_PLUGIN_IS_LOCAL=""
592✔
788
BEE_RESOLVE_PLUGIN_BASE_PATH=""
592✔
789
BEE_RESOLVE_PLUGIN_FULL_PATH=""
592✔
790
BEE_RESOLVE_PLUGIN_JSON_PATH=""
592✔
791

792
bee::resolve_plugin() {
793
  local plugin="$1" plugin_name plugin_version plugin_path is_local
628✔
794
  local -i found=0
628✔
795
  for plugins_path in "${BEE_PLUGINS_PATHS[@]}"; do
1,988✔
796
    while read -r plugin_name plugin_version plugin_path is_local; do
1,296✔
797
      found=1
302✔
798
      BEE_RESOLVE_PLUGIN_NAME="${plugin_name}"
302✔
799
      BEE_RESOLVE_PLUGIN_VERSION="${plugin_version}"
302✔
800
      BEE_RESOLVE_PLUGIN_IS_LOCAL=${is_local}
302✔
801
      BEE_RESOLVE_PLUGIN_BASE_PATH="${plugin_path}"
302✔
802
      if (( BEE_RESOLVE_PLUGIN_IS_LOCAL )); then
302✔
803
        BEE_RESOLVE_PLUGIN_FULL_PATH="${BEE_RESOLVE_PLUGIN_BASE_PATH}/${BEE_RESOLVE_PLUGIN_NAME}.bash"
28✔
804
        BEE_RESOLVE_PLUGIN_JSON_PATH="${BEE_RESOLVE_PLUGIN_BASE_PATH}/plugin.json"
28✔
805
      else
806
        BEE_RESOLVE_PLUGIN_FULL_PATH="${BEE_RESOLVE_PLUGIN_BASE_PATH}/${BEE_RESOLVE_PLUGIN_VERSION}/${BEE_RESOLVE_PLUGIN_NAME}.bash"
274✔
807
        BEE_RESOLVE_PLUGIN_JSON_PATH="${BEE_RESOLVE_PLUGIN_BASE_PATH}/${BEE_RESOLVE_PLUGIN_VERSION}/plugin.json"
274✔
808
      fi
809
    done < <(bee::resolve "${plugin}" "${plugins_path}" "${plugin%:*}.bash" 1)
810
    (( found )) && break
1,296✔
811
  done
812
  if (( ! found )); then
628✔
813
    BEE_RESOLVE_PLUGIN_NAME=""
326✔
814
    BEE_RESOLVE_PLUGIN_VERSION=""
326✔
815
    BEE_RESOLVE_PLUGIN_IS_LOCAL=0
326✔
816
    BEE_RESOLVE_PLUGIN_BASE_PATH=""
326✔
817
    BEE_RESOLVE_PLUGIN_FULL_PATH=""
326✔
818
    BEE_RESOLVE_PLUGIN_JSON_PATH=""
326✔
819
  fi
820
}
821

822
BEE_LOAD_PLUGIN_NAME=""
592✔
823
BEE_LOAD_PLUGIN_PATH=""
592✔
824
BEE_LOAD_PLUGIN_JSON_PATH=""
592✔
825
declare -Ag BEE_LOAD_PLUGIN_LOADED=()
1,178✔
826
BEE_LOAD_PLUGIN_MISSING=()
827

828
bee::load_plugin() {
829
  local -i ignore_missing=${2:-0}
358✔
830
  BEE_LOAD_PLUGIN_MISSING=()
831
  bee::mapped_plugin "$1"
358✔
832
  if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
358✔
833
    BEE_LOAD_PLUGIN_NAME="${BEE_RESOLVE_PLUGIN_NAME}"
76✔
834
    BEE_LOAD_PLUGIN_PATH="${BEE_RESOLVE_PLUGIN_FULL_PATH}"
76✔
835
    BEE_LOAD_PLUGIN_JSON_PATH="${BEE_RESOLVE_PLUGIN_JSON_PATH}"
76✔
836
    bee::load_plugin_deps
76✔
837
    if [[ ${ignore_missing} -eq 0 && ${#BEE_LOAD_PLUGIN_MISSING[@]} -gt 0 ]]; then
142✔
838
      for missing in "${BEE_LOAD_PLUGIN_MISSING[@]}"; do
4✔
839
        bee::log_error "Missing plugin: '${missing}'"
4✔
840
      done
841
      return 1
2✔
842
    fi
843
  else
844
    BEE_LOAD_PLUGIN_NAME=""
282✔
845
    BEE_LOAD_PLUGIN_PATH=""
282✔
846
    BEE_LOAD_PLUGIN_JSON_PATH=""
282✔
847
  fi
848
}
849

850
bee::load_plugin_deps() {
851
  if [[ ! -v BEE_LOAD_PLUGIN_LOADED["${BEE_RESOLVE_PLUGIN_FULL_PATH}"] ]]; then
116✔
852
    bee::load_os "$(dirname "${BEE_RESOLVE_PLUGIN_FULL_PATH}")"
220✔
853
    source "${BEE_RESOLVE_PLUGIN_FULL_PATH}"
110✔
854
    BEE_LOAD_PLUGIN_LOADED["${BEE_RESOLVE_PLUGIN_FULL_PATH}"]=1
110✔
855
    if [[ -f "${BEE_RESOLVE_PLUGIN_JSON_PATH}" ]]; then
110✔
856
      for dep in $(jq -r '.dependencies[]?' "${BEE_RESOLVE_PLUGIN_JSON_PATH}"); do
78✔
857
        bee::mapped_plugin "${dep}"
52✔
858
        if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
52✔
859
          bee::load_plugin_deps
40✔
860
        else
861
          BEE_LOAD_PLUGIN_MISSING+=("${dep}")
12✔
862
        fi
863
      done
864
    fi
865
  fi
866
}
867

868
bee:map_bee_plugins() {
869
  [[ ! -v BEE_PLUGINS ]] || bee::map_plugins "${BEE_PLUGINS[@]}" >/dev/null
372✔
870
}
871

872
declare -Ag BEE_PLUGIN_MAP=()
1,178✔
873
declare -Ag BEE_PLUGIN_MAP_LOCK=()
1,178✔
874
declare -Ag BEE_PLUGIN_MAP_LATEST=()
1,178✔
875
declare -ag BEE_PLUGIN_MAP_CONFLICTS=()
1,178✔
876

877
bee::map_plugins() {
878
  bee::map_plugins_recursively "$@"
54✔
879
  if (( ${#BEE_PLUGIN_MAP_CONFLICTS[@]} )); then
54✔
880
    bee::log_warning "Version conflicts:" "${BEE_PLUGIN_MAP_CONFLICTS[*]}"
2✔
881
  fi
882
  for plugin_name in "${!BEE_PLUGIN_MAP_LATEST[@]}"; do
44✔
883
    if [[ -v BEE_PLUGIN_MAP_LOCK["${plugin_name}"] ]]
44✔
884
    then BEE_PLUGIN_MAP["${plugin_name}"]="${BEE_PLUGIN_MAP_LOCK["${plugin_name}"]}"
16✔
885
    else BEE_PLUGIN_MAP["${plugin_name}"]="${BEE_PLUGIN_MAP_LATEST["${plugin_name}"]}"
28✔
886
    fi
887
  done
888
  for plugin_name in "${!BEE_PLUGIN_MAP_LOCK[@]}"; do
68✔
889
    if [[ ! -v BEE_PLUGIN_MAP["${plugin_name}"] ]]; then
68✔
890
      BEE_PLUGIN_MAP["${plugin_name}"]="${BEE_PLUGIN_MAP_LOCK["${plugin_name}"]}"
52✔
891
    fi
892
  done
893
  for plugin_name in "${!BEE_PLUGIN_MAP[@]}"; do
96✔
894
    echo "${plugin_name}:${BEE_PLUGIN_MAP["${plugin_name}"]}"
96✔
895
  done
896
}
897

898
bee::map_plugins_recursively() {
899
  local plugin_name plugin_version
82✔
900
  local -a with_version=() without_version=()
246✔
901
  for plugin in "$@"; do
144✔
902
    if [[ "${plugin%:*}" == "${plugin##*:}" ]]
144✔
903
    then without_version+=("${plugin}")
60✔
904
    else with_version+=("${plugin}")
84✔
905
    fi
906
  done
907
  for plugin in "${with_version[@]}"; do
84✔
908
    plugin_name="${plugin%:*}"
84✔
909
    plugin_version="${plugin##*:}"
84✔
910
    [[ ! -v BEE_PLUGIN_MAP_LOCK["${plugin_name}"] || "${BEE_PLUGIN_MAP_LOCK["${plugin_name}"]}" != "${plugin_version}" ]] || continue
102✔
911
    [[ ! -v BEE_PLUGIN_MAP_LATEST["${plugin_name}"] || "${BEE_PLUGIN_MAP_LATEST["${plugin_name}"]}" != "${plugin_version}" ]] || continue
90✔
912
    bee::resolve_plugin "${plugin}"
74✔
913
    if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
74✔
914
      if [[ ! -v BEE_PLUGIN_MAP_LOCK["${BEE_RESOLVE_PLUGIN_NAME}"] ]]; then
70✔
915
        BEE_PLUGIN_MAP_LOCK["${BEE_RESOLVE_PLUGIN_NAME}"]="${BEE_RESOLVE_PLUGIN_VERSION}"
68✔
916
      else
917
        local locked_version="${BEE_PLUGIN_MAP_LOCK["${BEE_RESOLVE_PLUGIN_NAME}"]}"
2✔
918
        local resolved_version="${BEE_RESOLVE_PLUGIN_VERSION}"
2✔
919
        if [[ "${locked_version}" != "${resolved_version}" ]]; then
2✔
920
          BEE_PLUGIN_MAP_LOCK["${BEE_RESOLVE_PLUGIN_NAME}"]="$(echo -e "${locked_version}\n${resolved_version}" | sort -rV | head -n 1)"
8✔
921
          BEE_PLUGIN_MAP_CONFLICTS+=("${BEE_RESOLVE_PLUGIN_NAME}:${locked_version} <-> ${BEE_RESOLVE_PLUGIN_NAME}:${resolved_version}")
2✔
922
        fi
923
      fi
924
      bee::map_plugin_dependencies
70✔
925
    fi
926
  done
927
  for plugin in "${without_version[@]}"; do
60✔
928
    [[ ! -v BEE_PLUGIN_MAP_LATEST["${plugin}"] ]] || continue
62✔
929
    bee::resolve_plugin "${plugin}"
58✔
930
    if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
58✔
931
      if [[ ${BEE_RESOLVE_PLUGIN_IS_LOCAL} -eq 0 && ! -v BEE_PLUGIN_MAP_LATEST["${BEE_RESOLVE_PLUGIN_NAME}"] ]]; then
94✔
932
        BEE_PLUGIN_MAP_LATEST["${BEE_RESOLVE_PLUGIN_NAME}"]="${BEE_RESOLVE_PLUGIN_VERSION}"
44✔
933
      fi
934
      bee::map_plugin_dependencies
50✔
935
    fi
936
  done
937
}
938

939
bee::map_plugin_dependencies() {
940
  if [[ -f "${BEE_RESOLVE_PLUGIN_JSON_PATH}" ]]; then
120✔
941
    local deps
28✔
942
    deps="$(jq -r '.dependencies[]?' "${BEE_RESOLVE_PLUGIN_JSON_PATH}")"
56✔
943
    if [[ -n "${deps}" ]]; then
28✔
944
      # shellcheck disable=SC2086
945
      bee::map_plugins_recursively ${deps}
28✔
946
    fi
947
  fi
948
}
949

950
bee::mapped_plugin() {
951
  local plugin="$1"
474✔
952
  if [[ -v BEE_PLUGIN_MAP["${plugin}"] ]]
474✔
953
  then bee::resolve_plugin "${plugin}:${BEE_PLUGIN_MAP["${plugin}"]}"
22✔
954
  else bee::resolve_plugin "${plugin}"
452✔
955
  fi
956
}
957

958
bee::run_plugin() {
959
  local plugin="$1"; shift
76✔
960
  if (( $# )); then
38✔
961
    [[ $(command -v "bee::secrets") == "bee::secrets" ]] && bee::secrets "${plugin}" "$@"
44✔
962
    local cmd="$1"; shift
40✔
963
    "${plugin}::${cmd}" "$@"
20✔
964
  else
965
    "${plugin}::help"
18✔
966
  fi
967
}
968

969
################################################################################
970
# pull
971
################################################################################
972

973
bee::pull::comp() {
974
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
4✔
975
    local cmd="${1:-}" comps=(--force "${BEE_HUBS[*]}")
4✔
976
    compgen -W "${comps[*]}" -- "${cmd}"
2✔
977
  else
978
    echo "${BEE_HUBS[*]}"
2✔
979
  fi
980
}
981

982
bee::prompt() {
983
  [[ -v BEE_FILE ]] || return 1
×
984
  local current_version latest_version
×
985
  current_version=$(bee::version)
×
986
  latest_version=$(bee::version --latest --cached)
×
987
  if [[ "${current_version}" == "${latest_version}" ]]
×
988
  then echo "${BEE_ICON} ${current_version}"
×
989
  else echo "${BEE_ICON} ${current_version}*"
×
990
  fi
991
}
992

993
bee::pull() {
994
  local -i force=0 pull=0
152✔
995
  while (( $# )); do
154✔
996
    case "$1" in
14✔
997
      --force) force=1; shift ;;
4✔
998
      --) shift; break ;; *) break ;;
12✔
999
    esac
1000
  done
1001

1002
  mkdir -p "${BEE_HUBS_CACHE_PATH}"
152✔
1003
  local cache_file="${BEE_HUBS_CACHE_PATH}/.bee_pull_cooldown"
152✔
1004

1005
  if (( force )); then
152✔
1006
    pull=1
2✔
1007
  else
1008
    local -i now ts delta
150✔
1009
    [[ ! -f "${cache_file}" ]] && echo "0" > "${cache_file}"
240✔
1010
    now=$(date +%s)
300✔
1011
    ts="$(cat "${cache_file}")"
300✔
1012
    delta=$(( now - ts ))
150✔
1013
    (( delta < BEE_HUB_PULL_COOLDOWN )) || pull=1
244✔
1014
  fi
1015

1016
  if (( pull )); then
152✔
1017
    local cache_path
96✔
1018
    for url in "${@:-"${BEE_HUBS[@]}"}"; do
182✔
1019
      cache_path="$(bee::to_cache_path "${url}")"
364✔
1020
      if [[ -n "${cache_path}" ]]; then
182✔
1021
        cache_path="${BEE_HUBS_CACHE_PATH}/${cache_path}"
180✔
1022
        if [[ -d "${cache_path}" ]]; then
180✔
1023
          pushd "${cache_path}" >/dev/null || exit 1
4✔
1024
            git pull
4✔
1025
          popd >/dev/null || exit 1
4✔
1026
        else
1027
          git clone "${url}" "${cache_path}" || true
246✔
1028
        fi
1029
      fi
1030
    done
1031
    date +%s > "${cache_file}"
96✔
1032
  fi
1033
}
1034

1035
################################################################################
1036
# res
1037
################################################################################
1038

1039
bee::res() {
1040
  if (( ! $# )); then
×
1041
    bee::help
×
1042
  else
1043
    local resources_dir target_dir
×
1044
    for plugin in "$@" ; do
×
1045
      bee::mapped_plugin "${plugin}"
×
1046
      if [[ -n "${BEE_RESOLVE_PLUGIN_FULL_PATH}" ]]; then
×
1047
        resources_dir="$(dirname "${BEE_RESOLVE_PLUGIN_FULL_PATH}")/resources"
×
1048
        if [[ -d "${resources_dir}" ]]; then
×
1049
          target_dir="${BEE_RESOURCES}/${BEE_RESOLVE_PLUGIN_NAME}"
×
1050
          echo "Copying resources into ${target_dir}"
×
1051
          mkdir -p "${target_dir}"
×
1052
          cp -r "${resources_dir}/". "${target_dir}/"
×
1053
        fi
1054
      fi
1055
    done
1056
  fi
1057
}
1058

1059
################################################################################
1060
# update
1061
################################################################################
1062

1063
bee::update::comp() {
1064
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
2✔
1065
    pushd "${BEE_SYSTEM_HOME}" >/dev/null || exit 1
×
1066
      git branch -r --format '%(refname:short)' \
×
1067
        | cut -d/ -f2- \
1068
        | tail -n +2
1069
    popd >/dev/null || exit 1
×
1070
  fi
1071
}
1072

1073
bee::update() {
1074
  local branch="${1:-main}"
×
1075
  pushd "${BEE_SYSTEM_HOME}" >/dev/null || exit 1
×
1076
    git switch "${branch}"
×
1077
    git pull
×
1078
    bee::log "bee is up-to-date and ready to bzzzz"
×
1079
  popd >/dev/null || exit 1
×
1080
}
1081

1082
################################################################################
1083
# version
1084
################################################################################
1085

1086
bee::version::comp() {
1087
  local cmd="${1:-}"
4✔
1088
  if (( ! $# || $# == 1 && COMP_PARTIAL )); then
4✔
1089
    echo --latest
2✔
1090
  elif (( $# == 1 || $# == 2 && COMP_PARTIAL )); then
2✔
1091
    case "${cmd}" in
2✔
1092
      --latest) echo "--cached" ;;
2✔
1093
    esac
1094
  fi
1095
}
1096

1097
bee::version() {
1098
  local -i latest=0 cached=0
46✔
1099
  while (( $# )); do
68✔
1100
    case "$1" in
24✔
1101
      --latest) latest=1; shift ;;
24✔
1102
      --cached) cached=1; shift ;;
20✔
1103
      --) shift; break ;; *) break ;;
2✔
1104
    esac
1105
  done
1106

1107
  if (( $# )); then
46✔
1108
    bee::help
2✔
1109
  elif (( latest )); then
44✔
1110
    if (( cached )); then
12✔
1111
      mkdir -p "${BEE_CACHES_PATH}"
10✔
1112
      local -i last_ts now delta
10✔
1113
      local cache cache_file="${BEE_CACHES_PATH}/.bee_latest_version_cache"
10✔
1114
      [[ ! -f "${cache_file}" ]] && echo "0,0" > "${cache_file}"
16✔
1115
      now=$(date +%s)
20✔
1116
      cache="$(cat "${cache_file}")"
20✔
1117
      last_ts="${cache%%,*}"
10✔
1118
      delta=$(( now - last_ts ))
10✔
1119
      if (( delta > BEE_LATEST_VERSION_CACHE_EXPIRE )); then
10✔
1120
        local version
8✔
1121
        version="$(curl -fsSL "${BEE_LATEST_VERSION_PATH}")"
16✔
1122
        echo "${now},${version}" > "${cache_file}"
8✔
1123
        echo "${version}"
8✔
1124
      else
1125
        echo "${cache##*,}"
2✔
1126
      fi
1127
    else
1128
      curl -fsSL "${BEE_LATEST_VERSION_PATH}"
2✔
1129
    fi
1130
  else
1131
    cat "${BEE_HOME}/version.txt"
32✔
1132
  fi
1133
}
1134

1135
################################################################################
1136
# wiki
1137
################################################################################
1138

1139
bee::wiki() {
1140
  if (( $# )); then
4✔
1141
    bee::help
2✔
1142
  else
1143
    os_open "${BEE_WIKI}"
2✔
1144
  fi
1145
}
1146

1147
################################################################################
1148
# traps
1149
################################################################################
1150

1151
declare -ig BEE_VERBOSE=0
592✔
1152
declare -ig BEE_CANCELED=0
592✔
1153
declare -igr BEE_MODE_INTERNAL=0
592✔
1154
declare -igr BEE_MODE_PLUGIN=1
592✔
1155
declare -ig BEE_MODE=${BEE_MODE_INTERNAL}
592✔
1156
declare -ig T=${SECONDS}
592✔
1157

1158
declare -Ag BEE_TRAPS_INT=()
1,178✔
1159
declare -Ag BEE_TRAPS_TERM=()
1,178✔
1160
declare -Ag BEE_TRAPS_EXIT=()
1,178✔
1161

1162
bee::add_int_trap() { BEE_TRAPS_INT["$1"]="$1"; }
1163
bee::add_term_trap() { BEE_TRAPS_TERM["$1"]="$1"; }
1164
bee::add_exit_trap() { BEE_TRAPS_EXIT["$1"]="$1"; }
1165
bee::remove_int_trap() { unset 'BEE_TRAPS_INT["$1"]'; }
1166
bee::remove_term_trap() { unset 'BEE_TRAPS_TERM["$1"]'; }
1167
bee::remove_exit_trap() { unset 'BEE_TRAPS_EXIT["$1"]'; }
1168

1169
bee::INT() {
1170
  BEE_CANCELED=1
×
1171
  for t in "${BEE_TRAPS_INT[@]}"; do
×
1172
    "$t"
×
1173
  done
1174
}
1175

1176
bee::TERM() {
1177
  BEE_CANCELED=1
×
1178
  for t in "${BEE_TRAPS_TERM[@]}"; do
×
1179
    "$t"
×
1180
  done
1181
}
1182

1183
bee::EXIT() {
1184
  local -i status=$?
508✔
1185
  for t in "${BEE_TRAPS_EXIT[@]}"; do "$t" ${status}; done
32✔
1186
  if (( ! BEE_QUIET && BEE_MODE == BEE_MODE_PLUGIN )); then
508✔
1187
    local duration="$(( SECONDS - T )) seconds"
6✔
1188
    if (( BEE_CANCELED )); then
6✔
1189
      bee::log_warning "bzzzz (${duration})"
×
1190
    else
1191
      if (( status )); then
6✔
1192
        bee::log_error "bzzzz ${status} (${duration})"
2✔
1193
      else
1194
        bee::log "bzzzz (${duration})"
4✔
1195
      fi
1196
    fi
1197
  fi
1198
}
1199

1200
################################################################################
1201
# completion
1202
################################################################################
1203

1204
declare -ag BEE_OPTIONS=(--batch --help --quiet --verbose)
1,178✔
1205
declare -ag BEE_COMMANDS=(cache env hash hubs info install job lint new plugins pull res update version wiki)
1,178✔
1206

1207
declare -ig COMP_PARTIAL=1
592✔
1208
# Add this to your .bashrc
1209
# complete -C bee bee
1210
bee::comp() {
1211
  # shellcheck disable=SC2207
1212
  local words=($(bee::split_args "${COMP_LINE}"))
228✔
1213
  local -i head=0 cursor=0
76✔
1214
  for word in "${words[@]}"; do
192✔
1215
    (( head += ${#word} + 1 ))
1216
    (( head <= COMP_POINT )) && (( cursor += 1 ))
1217
  done
1218
  local cur="${words[cursor]:-}" wordlist
76✔
1219
  (( cursor == ${#words[@]} )) && COMP_PARTIAL=0
152✔
1220
  if (( cursor == 1 )); then
76✔
1221
    # e.g. bee
1222
    local comps=("${BEE_OPTIONS[@]}" "${BEE_COMMANDS[@]}" "$(bee::comp_plugins)")
18✔
1223
    wordlist="${comps[*]}"
6✔
1224
  else
1225
    # e.g. bee install
1226
    wordlist="$(bee::comp_command_or_plugin "${words[1]}" "${words[@]:2}")"
140✔
1227
  fi
1228
  compgen -W "${wordlist}" -- "${cur}"
74✔
1229
}
1230

1231
bee::comp_plugins() {
1232
  compgen -A function |
82✔
1233
    grep --color=never '^[a-zA-Z]*::[a-zA-Z]' |
82✔
1234
    grep --color=never -v '^bee::' || true
162✔
1235

1236
  # shellcheck disable=SC2015
1237
  for plugins_path in "${BEE_PLUGINS_PATHS[@]}"; do
174✔
1238
    if [[ -d "${plugins_path}" ]]; then
174✔
1239
      find "${plugins_path}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;
74✔
1240
    fi
1241
  done
1242
}
1243

1244
bee::comp_plugin() {
1245
  local plugin="$1"
4✔
1246
  local -i n=$(( ${#plugin} + 3 ))
4✔
1247
  compgen -A function |
4✔
1248
    grep --color=never "^${plugin}::*" |
4✔
1249
    cut -c $n- || true
4✔
1250
}
1251

1252
bee::comp_command_or_plugin() {
1253
  local comps=("${BEE_OPTIONS[@]}" "${BEE_COMMANDS[@]}" "$(bee::comp_plugins)")
210✔
1254
  while (( $# )); do
82✔
1255
    case "$1" in
74✔
1256
      --batch) comps=("${comps[@]/--batch/--allow-fail}"); shift ;;
8✔
1257
      --allow-fail) comps=("${comps[@]/--allow-fail/}"); shift ;;
4✔
1258
      --help) return ;;
2✔
1259
      --quiet) comps=("${comps[@]/--quiet/}"); shift ;;
8✔
1260
      --verbose) comps=("${comps[@]/--verbose/}"); shift ;;
4✔
1261
      *) break ;;
60✔
1262
    esac
1263
  done
1264

1265
  if (( $# )); then
68✔
1266
    case "$1" in
60✔
1267
      cache) shift; bee::cache::comp "$@"; return ;;
18✔
1268
      env) shift; compgen -v; return ;;
×
1269
      hubs) shift; bee::hubs::comp "$@"; return ;;
24✔
1270
      info) shift; bee::info::comp "$@"; return ;;
18✔
1271
      install) shift; bee::install::comp "$@"; return ;;
12✔
1272
      job) shift; bee::job::comp "$@"; return ;;
24✔
1273
      pull) shift; bee::pull::comp "$@"; return ;;
12✔
1274
      plugins) shift; bee::plugins::comp "$@"; return ;;
30✔
1275
      res) shift; bee::hubs --list; return ;;
×
1276
      update) shift; bee::update::comp "$@"; return ;;
6✔
1277
      version) shift; bee::version::comp "$@"; return ;;
12✔
1278
    esac
1279

1280
    bee:map_bee_plugins
8✔
1281
    bee::load_plugin "$1"
8✔
1282
    if [[ -n "${BEE_LOAD_PLUGIN_NAME}" ]]; then
8✔
1283
      shift
8✔
1284
      local comp="${BEE_LOAD_PLUGIN_NAME}::comp"
8✔
1285
      if [[ $(command -v "${comp}") == "${comp}" ]]; then
16✔
1286
        "${comp}" "$@"
2✔
1287
      elif (( ! $# || $# == 1 && COMP_PARTIAL )); then
6✔
1288
        bee::comp_plugin "${BEE_LOAD_PLUGIN_NAME}"
4✔
1289
      fi
1290
      return
8✔
1291
    fi
1292

1293
    compgen -W "${comps[*]}" -- "$1"
×
1294
  else
1295
    echo "${comps[*]}"
8✔
1296
  fi
1297
}
1298

1299
################################################################################
1300
# run
1301
################################################################################
1302

1303
bee::batch() {
1304
  local -i allow_fail=0
52✔
1305
  while (( $# )); do
56✔
1306
    case "$1" in
56✔
1307
      --allow-fail) allow_fail=1; shift ;;
8✔
1308
      --) shift; break ;; *) break ;;
52✔
1309
    esac
1310
  done
1311

1312
  for batch in "$@"; do
114✔
1313
    local cmd="${batch%% *}"
114✔
1314
    local args="${batch#* }"
114✔
1315
    if [[ "${args}" != "${cmd}" ]]; then
114✔
1316
      # shellcheck disable=SC2046
1317
      if (( allow_fail ))
106✔
1318
      then bee::run "${cmd}" $(bee::split_args "${args}") || true
14✔
1319
      else bee::run "${cmd}" $(bee::split_args "${args}")
200✔
1320
      fi
1321
    else
1322
      if (( allow_fail ))
8✔
1323
      then bee::run "${cmd}" || true
4✔
1324
      else bee::run "${cmd}"
6✔
1325
      fi
1326
    fi
1327
  done
1328
}
1329

1330
bee::split_args() {
1331
  local IFS=' '
184✔
1332
  # shellcheck disable=SC2068
1333
  for arg in $@; do echo "${arg}"; done
804✔
1334
}
1335

1336
bee::run() {
1337
  if [[ -v COMP_LINE ]]; then
790✔
1338
    bee::comp
76✔
1339
    exit 0
64✔
1340
  fi
1341

1342
  trap bee::INT INT
714✔
1343
  trap bee::TERM TERM
714✔
1344
  trap bee::EXIT EXIT
714✔
1345

1346
  while (( $# )); do
760✔
1347
    case "$1" in
754✔
1348
      --batch) shift; bee::batch "$@"; return ;;
150✔
1349
      --help) bee::help; return ;;
4✔
1350
      --quiet) BEE_QUIET=1; shift ;;
80✔
1351
      --verbose) BEE_VERBOSE=1; shift ;;
12✔
1352
      --) shift; break ;; *) break ;;
660✔
1353
    esac
1354
  done
1355

1356
  if (( $# )); then
660✔
1357
    case "$1" in
652✔
1358
      cache) shift; bee::cache "$@"; return ;;
30✔
1359
      env) shift; bee::env "$@"; return ;;
126✔
1360
      hash) shift; bee::hash "$@"; return ;;
24✔
1361
      hubs) shift; bee::hubs "$@"; return ;;
30✔
1362
      info) shift; bee::info "$@"; return ;;
30✔
1363
      install) shift; bee::install "$@"; return ;;
162✔
1364
      job) shift; bee::job "$@"; return ;;
58✔
1365
      lint) shift; bee::lint "$@"; return ;;
94✔
1366
      new) shift; bee::new "$@"; return ;;
6✔
1367
      plugins) shift; bee:map_bee_plugins; bee::plugins "$@"; return ;;
118✔
1368
      prompt) shift; bee::prompt; return ;;
×
1369
      pull) shift; bee::pull "$@"; return ;;
288✔
1370
      res) shift; bee:map_bee_plugins; bee::res "$@"; return ;;
×
1371
      update) shift; bee::update "$@"; return ;;
×
1372
      version) shift; bee::version "$@"; return ;;
48✔
1373
      wiki) shift; bee::wiki "$@"; return ;;
12✔
1374
    esac
1375

1376
    # run bee plugin, e.g. bee github me
1377
    bee:map_bee_plugins
300✔
1378
    bee::load_plugin "$1"
300✔
1379
    if [[ -n "${BEE_LOAD_PLUGIN_NAME}" ]]; then
300✔
1380
      BEE_MODE=${BEE_MODE_PLUGIN}
30✔
1381
      shift
30✔
1382
      bee::run_plugin "${BEE_LOAD_PLUGIN_NAME}" "$@"
30✔
1383
      return
24✔
1384
    fi
1385
    # run args, e.g. bee echo "message"
1386
    "$@"
270✔
1387
  else
1388
    bee::help
8✔
1389
  fi
1390
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc