Skip to content

fix: make coverage baseline available #18

fix: make coverage baseline available

fix: make coverage baseline available #18

Workflow file for this run

name: CI
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
# The important one: prove that a clean machine can install multi-cli from
# scratch and immediately run its core commands. This is the check that would
# have caught the install failure (missing jq / broken launcher).
install-smoke:
name: install + run (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
# Force the installer's own jq-resolution path: both runner images ship
# jq pre-installed, so we remove it first. The installer must put jq back
# (apt-get on Linux, brew on macOS) or the whole CLI is dead on arrival.
- name: Remove pre-installed jq so the installer must provide it
run: |
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get remove -y jq || true
elif command -v brew >/dev/null 2>&1; then
brew uninstall --ignore-dependencies --force jq || true
fi
hash -r
if command -v jq >/dev/null 2>&1; then
echo "WARNING: jq still present at $(command -v jq); installer will detect-and-skip."
else
echo "jq removed; installer must reinstall it."
fi
- name: Install multi-cli from the working tree
run: bash scripts/install.sh --local
- name: jq is available after install
run: |
command -v jq
jq --version
# The launcher lands in ~/.local/bin (see install.sh BIN_LINK default).
- name: doctor / tools / new must all exit 0
run: |
export PATH="$HOME/.local/bin:$PATH"
multi-cli doctor
multi-cli tools
multi-cli new codex/ciprofile
# Shell correctness for every .sh and the main launcher. Run at error
# severity: the current tree has only style-level warnings (SC2155 "declare
# and assign separately" x24, SC2115 x1 in `multi-cli`) and zero errors, so
# error severity keeps CI green while still failing on real bugs (parse
# errors, undefined behavior, CRLF/SC1017, bad redirects, etc.).
shellcheck:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt-get update -y && sudo apt-get install -y shellcheck
- name: Lint shell scripts
run: shellcheck --severity=error multi-cli scripts/install.sh scripts/uninstall.sh
# PowerShell static analysis for the Windows scripts. Run at Error severity
# and exclude the rules that flag accepted design choices, not bugs:
# PSAvoidUsingConvertToSecureStringWithPlainText (Error-severity, fires on
# multi-cli.ps1's credential handling) and PSUseShouldProcessForStateChanging-
# Functions. Excluding these keeps CI green while still catching genuine
# parse/syntax failures and other error-severity rules.
pwsh-lint:
name: PSScriptAnalyzer
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install PSScriptAnalyzer
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
- name: Analyze PowerShell scripts
shell: pwsh
run: |
$targets = @(
'scripts/install.ps1',
'scripts/uninstall.ps1',
'multi-cli.ps1'
)
$excludedRules = @(
'PSAvoidUsingConvertToSecureStringWithPlainText',
'PSUseShouldProcessForStateChangingFunctions'
)
$findings = foreach ($target in $targets) {
Invoke-ScriptAnalyzer -Path $target -Severity Error -ExcludeRule $excludedRules
}
if ($findings) {
$findings | Format-Table -AutoSize | Out-String | Write-Host
throw "PSScriptAnalyzer reported $($findings.Count) error-severity finding(s)."
}
Write-Host "No error-severity findings."
# The bash session-continuation suite (bats). Runs the REAL launcher against
# real fixture trees on both Linux and macOS. bats-core and jq are fetched
# fresh by the runner (never committed): jq via the system package manager,
# bats-core via the system package manager when available, otherwise
# tests/run-bats.sh clones the pinned v1.11.0 into tests/vendor on demand.
bats:
name: bats (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Install bats + jq
run: |
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update -y
# libsecret-tools + gnome-keyring + dbus-x11: the auth/processSecret
# tests exercise the real Linux Secret Service backend (secret-tool),
# which needs both the CLI and a running keyring daemon.
sudo apt-get install -y bats jq libsecret-tools gnome-keyring dbus-x11 acl
elif command -v brew >/dev/null 2>&1; then
brew install bats-core jq
else
echo "no supported package manager (apt-get/brew) on this runner" >&2
exit 1
fi
bats --version
jq --version
- name: Validate adapter contracts
run: bash scripts/validate-adapters.sh
# macOS: `security` against the default login keychain can block on a GUI
# unlock/access prompt on a headless runner, hanging the whole suite.
# Give the run a dedicated ephemeral keychain with a known password,
# unlocked, partition-listed, and made default so no prompt can appear.
- name: Prepare an ephemeral CI keychain (macOS)
if: runner.os == 'macOS'
run: |
KEYCHAIN="multicli-ci-$(uuidgen).keychain"
KEYCHAIN_PASSWORD="multicli-ci"
PREVIOUS_DEFAULT="$(security default-keychain -d user | tr -d '"' | xargs)"
echo "MULTICLI_CI_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
echo "MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN=$PREVIOUS_DEFAULT" >> "$GITHUB_ENV"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security default-keychain -d user -s "$KEYCHAIN"
# shellcheck disable=SC2046
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
- name: Run the bats suite
run: |
if [ "$(uname -s)" = "Linux" ]; then
# Secret Service needs a session bus and an unlocked keyring.
# Smoke-check the store first so a broken keyring setup fails here
# with a clear error instead of obscure test failures.
dbus-run-session -- bash -c '
set -euo pipefail
printf "\n" | gnome-keyring-daemon --unlock --components=secrets >/dev/null
printf "ci-smoke" | secret-tool store --label=ci-smoke service multicli-ci target smoke
[ "$(secret-tool lookup service multicli-ci target smoke)" = "ci-smoke" ]
secret-tool clear service multicli-ci target smoke
exec bash tests/run-bats.sh
'
else
env MULTICLI_MACOS_KEYCHAIN="$HOME/Library/Keychains/$MULTICLI_CI_KEYCHAIN-db" bash tests/run-bats.sh
fi
- name: Run the real POSIX OS-user lifecycle
if: runner.os == 'Windows'
timeout-minutes: 10
run: bash tests/e2e/posix-osuser.sh
- name: Remove the ephemeral CI keychain (macOS)
if: runner.os == 'macOS' && always()
run: |
if [ -n "${MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN:-}" ]; then
security default-keychain -d user -s "$MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN"
fi
if [ -n "${MULTICLI_CI_KEYCHAIN:-}" ]; then
security delete-keychain "$MULTICLI_CI_KEYCHAIN"
fi
bash-coverage:
name: bash coverage (ubuntu-latest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Install coverage dependencies
run: |
BASHCOV_VERSION=3.3.0
BASHCOV_SHA256=9a761b6e36598f74897ead922cc04f85de3d1362f54a595c8eb6fcf5cb139c31
BASHCOV_GEM="$RUNNER_TEMP/bashcov.gem"
SIMPLECOV_VERSION=0.22.0
SIMPLECOV_SHA256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
SIMPLECOV_GEM="$RUNNER_TEMP/simplecov.gem"
sudo apt-get update -y
sudo apt-get install -y bats jq libsecret-tools gnome-keyring dbus-x11 acl ruby
curl -fsSL "https://rubygems.org/gems/bashcov-$BASHCOV_VERSION.gem" -o "$BASHCOV_GEM"
curl -fsSL "https://rubygems.org/gems/simplecov-$SIMPLECOV_VERSION.gem" -o "$SIMPLECOV_GEM"
printf '%s %s\n' "$BASHCOV_SHA256" "$BASHCOV_GEM" | sha256sum --check --strict
printf '%s %s\n' "$SIMPLECOV_SHA256" "$SIMPLECOV_GEM" | sha256sum --check --strict
sudo gem install "$SIMPLECOV_GEM" --no-document
sudo gem install --local "$BASHCOV_GEM" --no-document
bashcov --version
- name: Enforce Bash coverage
env:
COVERAGE_BASELINE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD^' }}
run: |
dbus-run-session -- bash -c '
set -euo pipefail
printf "\n" | gnome-keyring-daemon --unlock --components=secrets >/dev/null
exec env COVERAGE_BASELINE="$COVERAGE_BASELINE" bash tests/coverage/run-bash-coverage.sh
'
# The PowerShell session-continuation suite (Pester). Uses the Pester 3.4
# shipped with Windows PowerShell 5.1 (the suite is 3.4-compatible). The
# PowerShell launcher parses JSON natively, so this suite needs no jq.
pester:
name: pester (windows-latest)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install Pester 3.4.0 (matches the 3.x-compatible suite)
shell: powershell
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-PackageProvider -Name NuGet -Force -Scope CurrentUser
Install-Module -Name Pester -RequiredVersion 3.4.0 -Force -SkipPublisherCheck -Scope CurrentUser
- name: Validate adapter contracts
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts/Validate-Adapters.ps1
- name: Run the Pester suite
timeout-minutes: 30
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests/run-pester.ps1 -CI
- name: Enforce PowerShell module coverage
shell: powershell
env:
COVERAGE_BASELINE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD^' }}
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests/coverage/Invoke-ModuleCoverage.ps1 -MinimumPercent 95
windows-install-smoke:
name: install + run (windows-latest)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install from working tree
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install.ps1 -Local
- name: Validate installed launcher
shell: powershell
run: |
powershell -NoProfile -ExecutionPolicy Bypass -File multi-cli.ps1 doctor
powershell -NoProfile -ExecutionPolicy Bypass -File multi-cli.ps1 tools