Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b861421
Make the ShellCheck gate green and purge bash-4 syntax
marcepartment Aug 31, 2026
83c93f7
Probe for the MariaDB 11 client binaries in roll db
marcepartment Aug 31, 2026
feb596d
Layer the linux compose fragments on WSL too
marcepartment Aug 31, 2026
196213b
Add roll env sh as sugar for exec -T <service> sh -c
marcepartment Aug 31, 2026
2e5f18d
Add myloader --normalize-source-ddl for MySQL 8 dumps
marcepartment Aug 31, 2026
b596dce
Record two defects found while fixing the ShellCheck gate
marcepartment Aug 31, 2026
36ee8ed
Remove the findings fixed by milestones 1, 2, 6, 9 and 20
marcepartment Aug 31, 2026
df648b3
Probe the registry over HTTPS instead of pinging
marcepartment Aug 31, 2026
c228bde
Run CI on macOS as well, with a Docker-free smoke suite
marcepartment Aug 31, 2026
01a36f4
Fix the config load order and stop defaulting service versions silently
marcepartment Aug 31, 2026
b8c9c09
Small-fix bundle: quoting, defaults and hygiene
marcepartment Aug 31, 2026
1d53075
Validate the env name and resolve containers through Compose
marcepartment Aug 31, 2026
9523e88
Complete the command inventory and document the local env type
marcepartment Aug 31, 2026
b597f77
Remove the findings fixed by milestones 5, 7 and 10
marcepartment Aug 31, 2026
c2b5fd9
Add utils/interact.sh, the single prompt and UI library
marcepartment Aug 31, 2026
b3f617e
Declare gum as a dependency and check for it on install
marcepartment Aug 31, 2026
439e797
Route every prompt through interact.sh
marcepartment Aug 31, 2026
f49a226
Fix --help forking until killed, and two help files unparseable on macOS
marcepartment Aug 31, 2026
eac809f
Add service healthchecks so env up --wait means something
marcepartment Aug 31, 2026
8555185
Add --format json to status and describe
marcepartment Aug 31, 2026
f66f67d
Remove the feature requests implemented by milestones 14 and 16
marcepartment Aug 31, 2026
20bfa75
Add ROLL_PUBLISH_PORTS and configurable search-engine heap
marcepartment Aug 31, 2026
6db63bc
Give the registry real metadata, JSON output and has-command
marcepartment Aug 31, 2026
00651f3
Remove the findings fixed by milestones 15 and 18
marcepartment Aug 31, 2026
6ba45af
Add roll env doctor
marcepartment Aug 31, 2026
65edc02
Document the machine interface
marcepartment Aug 31, 2026
a4d2d5c
Move copyfromcontainer and copytocontainer into rolldev
marcepartment Aug 31, 2026
15108f0
Move magento2 theme into rolldev and drop the dialog dependency
marcepartment Aug 31, 2026
4452f9f
Move convert into rolldev
marcepartment Aug 31, 2026
b426751
List the commands moved in from RECLU
marcepartment Aug 31, 2026
9f006ef
Extract the backup family shared library and collapse restore-full
marcepartment Aug 31, 2026
4353957
Record that a restored search volume is unwritable
marcepartment Aug 31, 2026
0c1e74c
Split magento2-init into re-runnable phases
marcepartment Aug 31, 2026
b58c37f
Make tableplus the canonical local launcher and keep the password out…
marcepartment Sep 1, 2026
438751f
Close the fixed findings and draft the 0.8.0 release notes
marcepartment Sep 1, 2026
6facf34
Add CHANGELOG.md assembled from tags, commits and releases
marcepartment Sep 1, 2026
f38f8e4
Make the Unreleased changelog entry actually cover the branch
marcepartment Sep 1, 2026
5e860de
Fix the review findings in the moved-in commands and the pin check
marcepartment Sep 1, 2026
dd5bb12
Bring the 0.8.0 changelog and contributor docs back in line
marcepartment Sep 1, 2026
0d521d3
Make the Ubuntu leg of the ShellCheck workflow pass
marcepartment Sep 1, 2026
d563972
Permissions fix for backups
marcepartment Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/scripts/smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
#
# Docker-free smoke test for the `roll` CLI.
#
# Exercises commands that need neither a running Docker daemon nor an existing project checkout,
# so platform-specific regressions (bash 3.2 syntax, BSD vs GNU tool differences) surface without
# needing the full Docker-backed environment stack. Run from CI (macos-latest + ubuntu-latest) or
# locally.
#
# Usage: smoke.sh [bash32]
# (no argument) invoke roll natively, i.e. via its own #!/usr/bin/env bash shebang
# bash32 invoke roll explicitly under /bin/bash, to exercise bash 3.2.57 on macOS rather
# than whatever newer bash happens to be first on PATH

set -eu

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
ROLL_DIR="$(cd -- "${SCRIPT_DIR}/../.." && pwd -P)"

MODE="${1:-native}"

case "${MODE}" in
bash32)
ROLL_BIN=(/bin/bash "${ROLL_DIR}/bin/roll")
;;
native)
ROLL_BIN=("${ROLL_DIR}/bin/roll")
;;
*)
>&2 echo "Usage: $0 [bash32]"
exit 1
;;
esac

run() {
echo "+ ${ROLL_BIN[*]} $*"
"${ROLL_BIN[@]}" "$@"
}

run version
run config schema >/dev/null
run registry validate
run registry categories >/dev/null
run registry paths >/dev/null

TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/roll-smoke.XXXXXX")"
cleanup() {
rm -rf -- "${TMP_DIR}"
}
trap cleanup EXIT

(
cd -- "${TMP_DIR}"
"${ROLL_BIN[@]}" env-init smoketest magento2
"${ROLL_BIN[@]}" config validate
)

"$(dirname "$0")/test-syntax.sh"
"$(dirname "$0")/test-interact.sh" < /dev/null

echo "Smoke test passed (${MODE} mode)."
74 changes: 74 additions & 0 deletions .github/scripts/test-interact.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Asserts the non-interactive contract of utils/interact.sh: a value supplied by flag or env wins
# without ever reaching gum, and a prompt that cannot be answered fails naming the flag that would
# have answered it. This is the half of the interaction layer that automation depends on, and the
# half a TTY-less CI run can actually verify.
set -u
ROLL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
export ROLL_DIR
. "$ROLL_DIR/utils/core.sh"
. "$ROLL_DIR/utils/interact.sh"

pass=0; fail=0
check() { if [[ "$2" == "$3" ]]; then echo " PASS $1"; pass=$((pass+1)); else echo " FAIL $1 (got '$2', want '$3')"; fail=$((fail+1)); fi; }

echo "== isInteractive with stdin from /dev/null =="
if isInteractive; then r=interactive; else r=non-interactive; fi
check "reports non-interactive" "$r" "non-interactive"

echo "== preset value short-circuits, gum never invoked =="
# Remove gum from PATH entirely: if the wrapper touches gum at all, this explodes.
ORIG_PATH="$PATH"
export PATH=/usr/bin:/bin
MYVAR="already-set"
out=$(promptInput MYVAR "--name" "Name:" 2>&1); st=$?
check "promptInput exit status" "$st" "0"
check "promptInput left value intact" "$MYVAR" "already-set"

CHOICE="magento2"
promptChoose CHOICE "--type" "Pick a type" php laravel magento2 >/dev/null 2>&1; st=$?
check "promptChoose exit status" "$st" "0"
check "promptChoose left value intact" "$CHOICE" "magento2"

PW="hunter2"
promptPassword PW "--encrypt=" "Password:" >/dev/null 2>&1; st=$?
check "promptPassword exit status" "$st" "0"
check "promptPassword left value intact" "$PW" "hunter2"
export PATH="$ORIG_PATH"

echo "== no preset + no tty = hard error naming the flag =="
UNSET_A=""
out=$( (promptInput UNSET_A "--name <value>" "Name:") 2>&1 ); st=$?
check "promptInput exits 1" "$st" "1"
case "$out" in *"--name <value>"*) r=yes;; *) r=no;; esac
check "promptInput names the flag" "$r" "yes"

UNSET_B=""
out=$( (promptChoose UNSET_B "--type <type>" "Pick a type" php laravel) 2>&1 ); st=$?
check "promptChoose exits 1" "$st" "1"
case "$out" in *"--type <type>"*) r=yes;; *) r=no;; esac
check "promptChoose names the flag" "$r" "yes"

out=$( (promptConfirm "--force" "Overwrite?") 2>&1 ); st=$?
check "promptConfirm exits 1" "$st" "1"
case "$out" in *"--force"*) r=yes;; *) r=no;; esac
check "promptConfirm names the flag" "$r" "yes"

UNSET_C=""
out=$( (promptPassword UNSET_C "--encrypt=<password>" "Password:") 2>&1 ); st=$?
check "promptPassword exits 1" "$st" "1"
case "$out" in *"--encrypt=<password>"*) r=yes;; *) r=no;; esac
check "promptPassword names the flag" "$r" "yes"

echo "== boxes fall back to plain renderer with no tty =="
out=$(styledBox 2 "hello world" 2>&1)
case "$out" in *"| hello world |"*) r=plain;; *) r=other;; esac
check "styledBox uses plain box" "$r" "plain"

echo "== survives set -e =="
( set -e; styledBox 7 "x" >/dev/null; MYV=preset; promptInput MYV "--x" "X:" >/dev/null; echo ok ) >/dev/null 2>&1
check "no premature exit under set -e" "$?" "0"

echo ""
echo "passed=$pass failed=$fail"
[[ $fail -eq 0 ]]
54 changes: 54 additions & 0 deletions .github/scripts/test-syntax.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Parse every command, help and util file with the running bash.
#
# Worth its own step because two classes of defect are invisible to both ShellCheck and Ubuntu CI:
#
# 1. bash 3.2 mis-parses an apostrophe inside a heredoc nested in $( ), even when the heredoc
# delimiter is quoted. backup.help and status.help both shipped broken this way - `roll backup
# --help` and `roll status --help` exited 2 on macOS - while parsing cleanly under bash 5.
# 2. a .cmd that re-invokes `roll <itself> --help` recurses forever, because commands on
# ROLL_CMD_ANYARGS receive --help directly instead of roll rendering it. restore, restore-full
# and duplicate each did this, forking until killed.
#
# Run this under the system bash on macOS (bash 3.2) for the first check to mean anything.
set -eu

ROLL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${ROLL_DIR}"

failed=0

echo "bash ${BASH_VERSION} syntax check"
for file in bin/roll commands/*.cmd commands/*/*.cmd commands/*.help commands/*/*.help utils/*.sh; do
[[ -e "${file}" ]] || continue
if ! bash -n "${file}" 2>/dev/null; then
echo " FAIL ${file}"
bash -n "${file}" 2>&1 | sed 's/^/ /'
failed=$((failed + 1))
fi
done

echo "self-referential --help check"
for file in commands/*.cmd commands/*/*.cmd; do
[[ -e "${file}" ]] || continue
name="${file##*/}"
name="${name%.cmd}"
## Only commands on ROLL_CMD_ANYARGS receive --help themselves; for everything else bin/roll
## renders the help and the inner call terminates, so it is not a defect there.
case " $(sed -n 's/.*ROLL_CMD_ANYARGS=(\(.*\)).*/\1/p' bin/roll) " in
*" ${name} "*) ;;
*) continue ;;
esac
if grep -qE "^[[:space:]]*roll[[:space:]]+${name}[[:space:]]+(--help|-h)" "${file}"; then
echo " FAIL ${file} re-invokes 'roll ${name} --help', which recurses forever"
echo " source \"\${ROLL_DIR}/commands/usage.cmd\" instead"
failed=$((failed + 1))
fi
done

if [[ ${failed} -ne 0 ]]; then
echo "${failed} file(s) failed."
exit 1
fi

echo "All files parse and no command re-invokes its own help."
57 changes: 52 additions & 5 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,70 @@ name: ShellCheck
on:
push:
paths:
- 'commands/*.cmd'
- 'bin/roll'
- 'commands/**/*.cmd'
- 'commands/**/*.help'
- 'utils/*.sh'
- 'environments/**'
- '.github/scripts/**'
- '.shellcheckrc'
- '.github/workflows/shellcheck.yml'
pull_request:
paths:
- 'commands/*.cmd'
- 'bin/roll'
- 'commands/**/*.cmd'
- 'commands/**/*.help'
- 'utils/*.sh'
- 'environments/**'
- '.github/scripts/**'
- '.shellcheckrc'
- '.github/workflows/shellcheck.yml'
workflow_dispatch:

jobs:
shellcheck:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install shellcheck

- name: Install shellcheck (macOS)
if: runner.os == 'macOS'
run: brew install shellcheck

- name: Install shellcheck (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y shellcheck

- name: Run shellcheck
run: shellcheck commands/*.cmd utils/*.sh
run: shellcheck commands/*.cmd commands/*/*.cmd commands/*.help commands/*/*.help utils/*.sh .github/scripts/*.sh

# bin/roll requires a `docker` CLI on PATH (it runs `docker compose version` before
# dispatching to any subcommand, including Docker-free ones) but none of the smoke commands
# below touch a daemon. ubuntu-latest runners ship Docker already; macos-latest runners ship
# neither Docker nor GNU gettext (`envsubst`, needed by `env-init` for env types that carry
# an init.env), so install both as client-only tooling.
- name: Install Docker CLI + Compose plugin + gettext (macOS)
if: runner.os == 'macOS'
run: |
brew install docker docker-compose gettext
brew link --force gettext
mkdir -p "${HOME}/.docker"
cat > "${HOME}/.docker/config.json" <<EOF
{
"cliPluginsExtraDirs": ["$(brew --prefix)/lib/docker/cli-plugins"]
}
EOF
docker compose version

- name: Docker-free smoke test (native bash)
if: runner.os == 'Linux'
run: .github/scripts/smoke.sh native

- name: Docker-free smoke test (bash 3.2)
if: runner.os == 'macOS'
run: .github/scripts/smoke.sh bash32
117 changes: 117 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# ShellCheck configuration for this repository.
#
# The 10 errors ShellCheck found (SC2068, SC2242, SC2145, SC1128) have all been fixed in code —
# this file only disables warning/note/style classes that the codebase deliberately accepts.
# Do not add error-level codes here; fix those in the code instead.

# The `.cmd`/`.help` files are sourced into `bin/roll`'s shell rather than executed standalone, so
# many "unused" variables and functions are actually consumed by a sibling file after sourcing.
# ShellCheck cannot see across that boundary reliably even with -x, so this fires broadly.
disable=SC2034

# Word splitting/globbing on unquoted expansions is used throughout intentionally — several
# commands build `docker`/`compose` argument lists from unquoted variables that are expected to
# split into multiple words (e.g. compose file lists, flag strings). Auditing every occurrence is
# out of scope for this release; tracked as a future cleanup, not a release-blocker.
disable=SC2086
disable=SC2046

# `local foo="$(cmd)"` masking the command's exit status is a real but low-severity pattern used
# throughout for readability; none of the call sites currently depend on the masked status.
disable=SC2155

# `read` without `-r` is used only where the input is not expected to contain backslashes
# (prompts, simple confirmations); not worth the churn of auditing every call site this release.
disable=SC2162

# `$var` inside arithmetic contexts (`$((...))`) is used repeatedly for readability/consistency
# with the rest of the codebase; functionally harmless.
disable=SC2004

# `printf` format strings built from variables are used in a few places where the variable is a
# known, fixed format (not user-controlled), so there is no injection risk in practice.
disable=SC2059

# `[ -z "$(cmd | grep ...)" ]`-style existence checks read more consistently with this codebase's
# style than `! grep -q`; a stylistic preference, not a defect.
disable=SC2143

# `ls | grep` / `ls` for existence/listing checks is used against paths this tool itself manages
# (not arbitrary user filenames), so the non-alphanumeric-filename edge case does not apply.
disable=SC2010
disable=SC2012

# `cd` without `|| exit` is used only in subshells or where a failed `cd` is immediately followed
# by a command that would itself fail loudly; not worth the churn of auditing every call site.
disable=SC2164

# `${var/pattern}`-quoting nuance inside parameter expansion; the patterns used are static and do
# not contain characters that would be affected.
disable=SC2295

# Splitting command output into an array without `mapfile`/`read -a` is used against
# controlled/simple output (no embedded whitespace edge cases in practice).
disable=SC2207

# Dynamic `source` targets (config files, env fragments resolved at runtime) can't be statically
# followed by ShellCheck; this is expected, not a defect.
disable=SC1090
disable=SC1091

# A literal `{` following a variable in a few brace-expansion-like strings; the surrounding code
# is correct shell, just visually similar to a syntax error.
disable=SC1083

# A handful of `printf`/format-string argument-count mismatches are intentional (extra format
# specifiers are static decoration, not meant to consume an argument).
disable=SC2183

# `$(...)` used to force a subshell / capture-and-discard is intentional in the few spots it
# appears, not an accidental eval.
disable=SC2091

# `sed` used where a parameter expansion could do the same job; a stylistic preference, not a
# defect.
disable=SC2001

# A couple of `expr` uses predate this release's cleanup scope; functionally correct, just
# old-style arithmetic.
disable=SC2003

# `test -eq 0`/`-ne 0` against `$?` right after the command, instead of testing the command
# directly, is used in a couple of places for clarity when there's a comment on the exit code
# itself; harmless.
disable=SC2181

# `grep | wc -l` is used in a couple of places instead of `grep -c`; equivalent output, no
# performance concern at this scale.
disable=SC2126

# Single-quoted strings containing `$var`-looking text are literal examples/help text, not
# expansions the author forgot to escape.
disable=SC2016

# Subshells via `( ... )` are used deliberately in a couple of spots to scope variables/`cd`
# without affecting the caller's shell; the subshell overhead is negligible here.
disable=SC2233
disable=SC2235

# `echo $(cmd)` instead of `cmd` appears once; harmless, output is unchanged.
disable=SC2005

# A `$?` immediately after `echo`/`printf` referring to that command rather than the one before it
# appears once in output the caller does not use.
disable=SC2320

# A variable is used as an array in one context and reassigned as a plain string in another within
# the same script; both usages are correct for the way that variable is actually consumed there.
disable=SC2178

# `commands/magento2/magepack.cmd` ends with unreachable debug output left from earlier work; kept
# out of scope for this release, not treated as a defect worth fixing here.
disable=SC2317

# Service toggles such as ROLL_DB reach the command files as exports from utils/config.sh rather
# than by assignment, so ShellCheck reads them as possible misspellings of ROLL_DIR. Same
# sourced-into-bin/roll boundary as SC2034 above.
disable=SC2153
Loading
Loading