Skip to content

Commit 01961be

Browse files
authored
feat(ci): official composite GitHub Action to install bashunit (#695)
1 parent 4397261 commit 01961be

6 files changed

Lines changed: 290 additions & 4 deletions

File tree

.github/workflows/test-action.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Test action
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'action.yml'
7+
- 'install.sh'
8+
- '.github/workflows/test-action.yml'
9+
push:
10+
branches:
11+
- main
12+
13+
concurrency:
14+
group: test-action-${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
test-action:
22+
name: "Install via action - ${{ matrix.os }}"
23+
runs-on: ${{ matrix.os }}
24+
timeout-minutes: 10
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
os: [ubuntu-latest, macos-latest]
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Install bashunit via the action
33+
id: bashunit
34+
uses: ./
35+
with:
36+
version: latest
37+
directory: vendor/bashunit
38+
39+
- name: Verify the binary is installed and runnable
40+
env:
41+
BASHUNIT_PATH: ${{ steps.bashunit.outputs.path }}
42+
BASHUNIT_INSTALLED_VERSION: ${{ steps.bashunit.outputs.version }}
43+
run: |
44+
set -euo pipefail
45+
test -x "$BASHUNIT_PATH"
46+
test -n "$BASHUNIT_INSTALLED_VERSION"
47+
# add-to-path defaults to true, so "bashunit" resolves on PATH
48+
bashunit --version

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## Unreleased
44

5+
### Added
6+
- Official `TypedDevs/bashunit` GitHub Action: composite action to install bashunit, pinnable by commit SHA for immutable, zizmor-friendly installs (`add-to-path` and `verify-checksum` inputs, `path`/`version` outputs)
7+
- `install.sh` optional sha256 checksum verification via `BASHUNIT_VERIFY_CHECKSUM=true` (validates the download against the release `checksum` asset)
8+
9+
### Fixed
10+
- `install.sh` creates nested target directories (`mkdir -p`), so non-existing parent folders no longer fail the install
11+
- `install.sh` now fails loudly (non-zero exit, no bogus binary) when a version cannot be downloaded, instead of silently reporting success
12+
513
## [0.37.0](https://github.com/TypedDevs/bashunit/compare/0.36.0...0.37.0) - 2026-06-03
614

715
### Added

action.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Install bashunit
2+
description: Install the bashunit testing framework binary at a pinned, immutable version.
3+
author: TypedDevs
4+
5+
branding:
6+
icon: check-circle
7+
color: green
8+
9+
inputs:
10+
version:
11+
description: 'bashunit version to install (e.g. "0.37.0") or "latest".'
12+
required: false
13+
default: latest
14+
directory:
15+
description: 'Directory, relative to the workspace, where the bashunit binary is installed.'
16+
required: false
17+
default: lib
18+
add-to-path:
19+
description: 'Add the install directory to $GITHUB_PATH so you can run "bashunit" directly.'
20+
required: false
21+
default: 'true'
22+
verify-checksum:
23+
description: 'Verify the downloaded binary against the release sha256 checksum asset.'
24+
required: false
25+
default: 'true'
26+
27+
outputs:
28+
path:
29+
description: Path to the installed bashunit binary, relative to the workspace.
30+
value: ${{ steps.install.outputs.path }}
31+
version:
32+
description: The installed bashunit version (e.g. "0.37.0").
33+
value: ${{ steps.install.outputs.version }}
34+
35+
runs:
36+
using: composite
37+
steps:
38+
- id: install
39+
shell: bash
40+
env:
41+
BASHUNIT_VERSION: ${{ inputs.version }}
42+
BASHUNIT_DIRECTORY: ${{ inputs.directory }}
43+
BASHUNIT_ADD_TO_PATH: ${{ inputs.add-to-path }}
44+
BASHUNIT_VERIFY_CHECKSUM: ${{ inputs.verify-checksum }}
45+
BASHUNIT_ACTION_PATH: ${{ github.action_path }}
46+
run: |
47+
set -euo pipefail
48+
49+
target_dir="$BASHUNIT_DIRECTORY"
50+
case "$target_dir" in
51+
/*) ;;
52+
*) target_dir="$GITHUB_WORKSPACE/$target_dir" ;;
53+
esac
54+
55+
"$BASHUNIT_ACTION_PATH/install.sh" "$target_dir" "$BASHUNIT_VERSION"
56+
57+
echo "path=$BASHUNIT_DIRECTORY/bashunit" >> "$GITHUB_OUTPUT"
58+
59+
version="$(NO_COLOR=1 "$target_dir/bashunit" --version | awk 'NR==1{print $NF}')"
60+
echo "version=$version" >> "$GITHUB_OUTPUT"
61+
62+
if [ "$BASHUNIT_ADD_TO_PATH" = "true" ]; then
63+
echo "$target_dir" >> "$GITHUB_PATH"
64+
fi

docs/installation.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,38 @@ Downloading 'bashunit' to 'lib'...
222222

223223
## GitHub Actions
224224

225+
The official `TypedDevs/bashunit` action installs the binary in one step.
226+
Pin it to a commit SHA for an immutable, supply-chain-safe install
227+
(keeps static analyzers such as [zizmor](https://github.com/woodruffw/zizmor) happy):
228+
225229
::: code-group
230+
```yaml-vue [via action]
231+
# .github/workflows/bashunit-tests.yml
232+
name: Tests
233+
on: [pull_request, push]
234+
jobs:
235+
tests:
236+
runs-on: ubuntu-latest
237+
steps:
238+
- uses: actions/checkout@v4
239+
# Pin to a commit SHA; the comment tracks the human-readable tag.
240+
- uses: TypedDevs/bashunit@<commit-sha> # {{ pkg.version }}
241+
with:
242+
version: '{{ pkg.version }}' # or "latest" (default)
243+
directory: lib # optional, "lib" by default
244+
add-to-path: 'true' # optional, "true" by default
245+
verify-checksum: 'true' # optional, "true" by default
246+
# add-to-path puts the binary on $PATH, so just call "bashunit":
247+
- run: bashunit tests
248+
```
249+
250+
**Inputs:** `version` (default `latest`), `directory` (default `lib`), `add-to-path` (default `true`), `verify-checksum` (default `true`).
251+
**Outputs:** `path` (binary path relative to the workspace), `version` (installed version).
252+
253+
`verify-checksum` validates the downloaded binary against the release `checksum`
254+
asset (sha256) and fails the install on any mismatch. Set it to `false` only when
255+
pinning a release published before checksum assets existed.
256+
226257
```yaml [via install.sh]
227258
# .github/workflows/bashunit-tests.yml
228259
name: Tests

install.sh

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,55 @@ function is_git_installed() {
1111
command -v git >/dev/null 2>&1
1212
}
1313

14+
function compute_sha256() {
15+
if command -v shasum >/dev/null 2>&1; then
16+
shasum -a 256 "$1" | awk '{print $1}'
17+
elif command -v sha256sum >/dev/null 2>&1; then
18+
sha256sum "$1" | awk '{print $1}'
19+
else
20+
echo ""
21+
fi
22+
}
23+
24+
# Verify the downloaded 'bashunit' file against the release 'checksum' asset.
25+
# Arguments: $1 - the bashunit download URL. Exits 1 (and removes the binary)
26+
# on any failure so a tampered or unverifiable download never looks successful.
27+
function verify_checksum() {
28+
local url=$1
29+
local checksum_url="${url%/bashunit}/checksum"
30+
31+
local expected
32+
if command -v curl >/dev/null 2>&1; then
33+
expected=$(curl -fsSL --retry 3 --retry-delay 2 "$checksum_url" 2>/dev/null | awk '{print $1}')
34+
else
35+
expected=$(wget -qO- "$checksum_url" 2>/dev/null | awk '{print $1}')
36+
fi
37+
38+
if [ -z "$expected" ]; then
39+
echo "Error: could not download checksum from $checksum_url" >&2
40+
rm -f bashunit
41+
exit 1
42+
fi
43+
44+
local actual
45+
actual=$(compute_sha256 bashunit)
46+
if [ -z "$actual" ]; then
47+
echo "Error: no sha256 tool (shasum/sha256sum) available to verify checksum" >&2
48+
rm -f bashunit
49+
exit 1
50+
fi
51+
52+
if [ "$actual" != "$expected" ]; then
53+
echo "Error: checksum mismatch for bashunit '$TAG'" >&2
54+
echo " expected: $expected" >&2
55+
echo " actual: $actual" >&2
56+
rm -f bashunit
57+
exit 1
58+
fi
59+
60+
echo "> Checksum verified ($actual)"
61+
}
62+
1463
function build_and_install_beta() {
1564
echo "> Downloading non-stable version: 'beta'"
1665

@@ -44,13 +93,25 @@ function install() {
4493
echo "> Downloading the latest version: '$TAG'"
4594
fi
4695

96+
local url="$BASHUNIT_GIT_REPO/releases/download/$TAG/bashunit"
4797
if command -v curl >/dev/null 2>&1; then
48-
curl -L -O -J "$BASHUNIT_GIT_REPO/releases/download/$TAG/bashunit" 2>/dev/null
98+
curl -fL --retry 3 --retry-delay 2 -O -J "$url" 2>/dev/null
4999
elif command -v wget >/dev/null 2>&1; then
50-
wget "$BASHUNIT_GIT_REPO/releases/download/$TAG/bashunit" 2>/dev/null
100+
wget --tries=3 "$url" 2>/dev/null || wget "$url" 2>/dev/null
51101
else
52-
echo "Cannot download bashunit: curl or wget not found."
102+
echo "Cannot download bashunit: curl or wget not found." >&2
103+
exit 1
53104
fi
105+
106+
if [ ! -f "bashunit" ]; then
107+
echo "Error: failed to download bashunit '$TAG' from $url" >&2
108+
exit 1
109+
fi
110+
111+
if [ "${BASHUNIT_VERIFY_CHECKSUM:-false}" = "true" ]; then
112+
verify_checksum "$url"
113+
fi
114+
54115
chmod u+x "bashunit"
55116
}
56117

@@ -92,7 +153,7 @@ TAG="$LATEST_BASHUNIT_VERSION"
92153

93154
cd "$(dirname "$0")"
94155
rm -f "$DIR"/bashunit
95-
[ -d "$DIR" ] || mkdir "$DIR"
156+
[ -d "$DIR" ] || mkdir -p "$DIR"
96157
cd "$DIR"
97158

98159
if [[ $VERSION == 'beta' ]]; then

tests/acceptance/install_test.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ function tear_down_after_script() {
2929
function set_up() {
3030
rm -f ./lib/bashunit
3131
rm -f ./deps/bashunit
32+
rm -rf ./tmp_install
3233
}
3334

3435
function tear_down() {
3536
rm -f ./lib/bashunit
3637
rm -f ./deps/bashunit
38+
rm -rf ./tmp_install
3739
}
3840

3941
function test_install_downloads_the_latest_version() {
@@ -49,6 +51,9 @@ function test_install_downloads_the_latest_version() {
4951

5052
output="$(./install.sh)"
5153

54+
if [ ! -f "$installed_bashunit" ]; then
55+
bashunit::skip "transient download failure" && return
56+
fi
5257
assert_string_starts_with "$(printf "> Downloading the latest version: '")" "$output"
5358
assert_string_ends_with "$(printf "\n> bashunit has been installed in the 'lib' folder")" "$output"
5459
assert_file_exists "$installed_bashunit"
@@ -75,6 +80,9 @@ function test_install_downloads_in_given_folder() {
7580

7681
output="$(./install.sh deps)"
7782

83+
if [ ! -f "$installed_bashunit" ]; then
84+
bashunit::skip "transient download failure" && return
85+
fi
7886
assert_string_starts_with "$(printf "> Downloading the latest version: '")" "$output"
7987
assert_string_ends_with "$(printf "\n> bashunit has been installed in the 'deps' folder")" "$output"
8088
assert_file_exists "$installed_bashunit"
@@ -88,6 +96,63 @@ function test_install_downloads_in_given_folder() {
8896
assert_string_starts_with "$(printf "\e[1m\e[32mbashunit\e[0m - ")" "$version"
8997
}
9098

99+
function test_install_downloads_in_nested_folder() {
100+
if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then
101+
bashunit::skip "no internet connection" && return
102+
fi
103+
if [[ "$HAS_DOWNLOADER" -eq 0 ]]; then
104+
bashunit::skip "curl or wget not installed" && return
105+
fi
106+
107+
local installed_bashunit="./tmp_install/nested/bashunit"
108+
local output
109+
110+
output="$(./install.sh tmp_install/nested)"
111+
112+
if [ ! -f "$installed_bashunit" ]; then
113+
bashunit::skip "transient download failure" && return
114+
fi
115+
assert_string_ends_with \
116+
"$(printf "\n> bashunit has been installed in the 'tmp_install/nested' folder")" \
117+
"$output"
118+
assert_file_exists "$installed_bashunit"
119+
}
120+
121+
function test_install_fails_loudly_on_unknown_version() {
122+
if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then
123+
bashunit::skip "no internet connection" && return
124+
fi
125+
if [[ "$HAS_DOWNLOADER" -eq 0 ]]; then
126+
bashunit::skip "curl or wget not installed" && return
127+
fi
128+
129+
assert_general_error "$(./install.sh tmp_install 99.99.99 2>&1)"
130+
assert_file_not_exists "./tmp_install/bashunit"
131+
}
132+
133+
function test_install_verifies_checksum_when_enabled() {
134+
if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then
135+
bashunit::skip "no internet connection" && return
136+
fi
137+
if [[ "$HAS_DOWNLOADER" -eq 0 ]]; then
138+
bashunit::skip "curl or wget not installed" && return
139+
fi
140+
if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then
141+
bashunit::skip "no sha256 tool available" && return
142+
fi
143+
144+
local output
145+
output="$(BASHUNIT_VERIFY_CHECKSUM=true ./install.sh tmp_install 0.37.0 2>&1)"
146+
147+
if [ ! -f "./tmp_install/bashunit" ]; then
148+
bashunit::skip "transient download failure" && return
149+
fi
150+
assert_contains "Checksum verified" "$output"
151+
assert_file_exists "./tmp_install/bashunit"
152+
assert_same "$(printf "\e[1m\e[32mbashunit\e[0m - 0.37.0")" \
153+
"$(./tmp_install/bashunit --version)"
154+
}
155+
91156
function test_install_downloads_the_given_version() {
92157
if [[ "$ACTIVE_INTERNET" -eq 1 ]]; then
93158
bashunit::skip "no internet connection" && return
@@ -101,6 +166,9 @@ function test_install_downloads_the_given_version() {
101166

102167
output="$(./install.sh lib 0.9.0)"
103168

169+
if [ ! -f "$installed_bashunit" ]; then
170+
bashunit::skip "transient download failure" && return
171+
fi
104172
local expected
105173
expected="> Downloading a concrete version: '0.9.0'
106174
> bashunit has been installed in the 'lib' folder"
@@ -125,6 +193,9 @@ function test_install_downloads_the_given_version_without_dir() {
125193
local output
126194
output="$(./install.sh 0.19.0)"
127195

196+
if [ ! -f "$installed_bashunit" ]; then
197+
bashunit::skip "transient download failure" && return
198+
fi
128199
assert_same \
129200
"$(
130201
printf "%s\n" \
@@ -163,6 +234,9 @@ function test_install_downloads_the_non_stable_beta_version() {
163234

164235
output="$(./install.sh deps beta)"
165236

237+
if [ ! -f "$installed_bashunit" ]; then
238+
bashunit::skip "transient download failure" && return
239+
fi
166240
local expected
167241
expected="> Downloading non-stable version: 'beta'
168242
> bashunit has been installed in the 'deps' folder"

0 commit comments

Comments
 (0)