Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 48 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ concurrency:

jobs:
validate:
runs-on: macos-latest
name: Validate (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -27,3 +34,43 @@ jobs:
run: python -m pip install ".[dev]"
- name: Run Python tests
run: python -m pytest

linux-distributions:
name: Validate (${{ matrix.name }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Debian 12
image: debian:12-slim
family: debian
- name: Fedora latest
image: fedora:latest
family: fedora
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Run distribution validation in Docker
env:
DISTRO_IMAGE: ${{ matrix.image }}
DISTRO_FAMILY: ${{ matrix.family }}
run: |
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env DISTRO_FAMILY \
"$DISTRO_IMAGE" \
sh -lc '
set -eu
if [ "$DISTRO_FAMILY" = debian ]; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y bash python3 python3-pip python3-venv
else
dnf install -y python3 python3-pip
fi
./tests/validate.sh
python3 -m venv /tmp/base-cli-venv
/tmp/base-cli-venv/bin/python -m pip install ".[dev]"
/tmp/base-cli-venv/bin/python -m pytest
/tmp/base-cli-venv/bin/python -c "import base_cli; print(base_cli.__version__)"
'
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and versions are tracked in the repo-root `VERSION` file.
locking backends.
- Make terminal detection tolerate closed streams and record `COMSPEC` when
Windows has no `SHELL` environment variable.
- Add Linux distribution and WSL2 validation guidance for the generic package.

## [0.2.0] - 2026-08-01

Expand Down
27 changes: 27 additions & 0 deletions docs/platform-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Platform support

`base-cli` is a pure-Python framework. Its Linux support is distribution-neutral
and is validated on Ubuntu, Debian, and Fedora-family environments. The
package does not install or manage operating-system packages; consumers remain
responsible for Python and any external tools their commands need.

## WSL2

WSL2 is supported when Python runs inside the Linux distribution. Validate a
checkout from the WSL shell with:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install ".[dev]"
.venv/bin/python -m pytest
```

Prefer a checkout in the WSL filesystem (for example, under `~/work`) for
normal development. Windows-mounted paths such as `/mnt/c` remain usable, but
their filesystem performance, case-sensitivity, and permission behavior are
provided by the Windows mount and are outside the Linux filesystem contract.

WSL2 support does not imply that the generic package translates paths between
Linux and Windows or that a consumer's native Windows commands are available
inside the distribution.
23 changes: 11 additions & 12 deletions tests/test_app_runtime_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def main(ctx: base_cli.Context) -> None:
invoke(app, [])

@unittest.skipUnless(importlib.util.find_spec("click"), "Click is not installed")
def test_run_app_reports_unwritable_cache_root_without_traceback(self) -> None:
def test_run_app_reports_unusable_cache_root_without_traceback(self) -> None:
app = generic_app(name="cache-failure", version="0.1.0")

@app.command()
Expand All @@ -55,18 +55,17 @@ def main(ctx: base_cli.Context) -> None:
home = root / "home"
cache_root = root / "cache-root"
home.mkdir()
cache_root.mkdir()
cache_root.chmod(0o500)
# A regular file is unusable as a cache root on every platform and
# also behaves consistently when the test suite runs as root in a
# Linux distribution container (where mode bits are bypassed).
cache_root.write_text("not a directory", encoding="utf-8")
stderr = io.StringIO()
try:
with mock.patch.dict(os.environ, {"HOME": str(home), "BASE_CLI_CACHE_DIR": str(cache_root)}):
with redirect_stderr(stderr):
try:
exit_code = base_cli.run_app(app, [])
except PermissionError as exc:
self.fail(f"run_app should handle context creation permission errors: {exc}")
finally:
cache_root.chmod(0o700)
with mock.patch.dict(os.environ, {"HOME": str(home), "BASE_CLI_CACHE_DIR": str(cache_root)}):
with redirect_stderr(stderr):
try:
exit_code = base_cli.run_app(app, [])
except PermissionError as exc:
self.fail(f"run_app should handle context creation permission errors: {exc}")

error = stderr.getvalue()
self.assertEqual(exit_code, 1)
Expand Down