Skip to content

Remove code duplication across CLI modules #159

Remove code duplication across CLI modules

Remove code duplication across CLI modules #159

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
types: [opened, reopened, ready_for_review, synchronize]
push:
branches: [main] # PRs are covered by pull_request (incl. synchronize);
# scoping push to main avoids double-running every PR commit.
# Least privilege: CI only needs to read the repo. Actions are pinned to commit
# SHAs (a moved tag can't silently change what runs); Dependabot keeps them current.
permissions:
contents: read
# Cancel superseded runs when new commits land on a PR/branch, but never cancel a
# main run (don't kill an in-flight merge build).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
name: lint + typecheck + tests (py${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
# Test both ends of the supported range: 3.12 is the floor (requires-python),
# 3.13 is what the Homebrew formula ships. fail-fast off so one version's
# failure doesn't mask the other's.
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13"]
# Pin the interpreter every `uv run`/`uv build` in check.sh resolves to, so the
# matrix actually exercises each version rather than whatever uv would pick.
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
# PortAudio backs sounddevice; ffmpeg decodes non-WAV/URL audio (the `--sample`
# stream tests build a FileSource for the hosted sample, which needs ffmpeg).
- name: System deps (PortAudio + ffmpeg)
run: sudo apt-get update && sudo apt-get install -y libportaudio2 ffmpeg
# check.sh lints Markdown and template JS/CSS via Node CLIs; pin to the
# versions used locally. The runner ships Node, so a global npm install suffices.
- name: Node lint CLIs
run: npm install -g markdownlint-cli@0.45.0 prettier@3.8.3
# check.sh runs every tool through `uv run` / `uv build` for a locked,
# reproducible env, so only uv must be on PATH (installed from PyPI to match
# the repo's pip-based, no-new-action posture). `uv run` itself syncs the
# project + dev group into .venv, so no `pip install -e .` is needed here.
- name: Install
run: python -m pip install uv
- name: Lint, typecheck, test
run: ./scripts/check.sh
# Branch protection requires a check literally named "lint + typecheck + tests",
# but `check` is a matrix, so its contexts are suffixed "(py3.12)" / "(py3.13)".
# Re-publish the un-suffixed name here, green only when every matrix cell passed
# (if: always() + an explicit result check, so a failed/skipped/cancelled matrix
# can't satisfy the required check). Point branch protection at this one stable
# name and matrix changes never break the required check again.
check-result:
name: lint + typecheck + tests
needs: [check]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require every py-version matrix cell to have passed
run: |
if [ "${{ needs.check.result }}" != "success" ]; then
echo "check matrix result: ${{ needs.check.result }}"
exit 1
fi
echo "all py-version matrix cells passed"
lint-formula:
name: brew style (Homebrew formula)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
# Homebrew's formula linters live inside `brew`, so set it up on the runner.
# Homebrew/actions is a monorepo (setup-homebrew is a subpath); pin it to a
# commit SHA like every other action here — Dependabot keeps it current.
- uses: Homebrew/actions/setup-homebrew@2ebcf16054461267868620b1414507f3ccc765c1
# `brew style` is the RuboCop-based formula linter and runs fully offline, so
# it lints idioms (resource/depends_on ordering, on_linux scoping) on every PR.
# The stricter `brew audit --strict --online` and a real `brew install`/`brew
# test` build are deliberately NOT here: they need the source `sha256`, which
# stays a placeholder until the v0.1.0 release tag is cut. Those belong in a
# release-time job (see the Homebrew tap plan, Task 4/6).
- name: Lint the formula
run: brew style ./Formula/aai.rb
pre-commit:
name: pre-commit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
# PortAudio backs sounddevice; ffmpeg decodes the `--sample` stream source.
- name: System deps (PortAudio + ffmpeg)
run: sudo apt-get update && sudo apt-get install -y libportaudio2 ffmpeg
# The local pytest hook runs `python -m pytest`, so the package + dev group
# must be importable. `pip install --group` needs pip >= 25.1, so upgrade first.
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install -e . --group dev
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
build:
name: build + twine check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
- name: Build wheel + sdist
run: |
python -m pip install build twine
python -m build
- name: Validate metadata
run: twine check dist/*
audit:
name: pip-audit (dependency CVEs)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
- name: Audit runtime dependencies for known CVEs
run: |
# Keep build tooling current first: pip-audit scans the whole environment,
# so a pip/setuptools advisory that a one-line upgrade fixes would otherwise
# fail the gate on something that isn't one of our runtime dependencies.
python -m pip install --upgrade pip setuptools
python -m pip install -e . pip-audit
# Append `--ignore-vuln <ID>` to accept an unfixable transitive advisory.
python -m pip_audit
install-smoke:
name: install.sh real install (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
kfilter: "" # both branches: pipx + pip --user
- os: macos-latest
kfilter: "-k pipx" # pipx only — PEP 668 makes pip --user flaky on macOS
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
# `aai version` imports the package, which pulls in sounddevice (needs
# PortAudio) and ffmpeg-backed sources. Match the other jobs' system deps.
- name: System deps (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libportaudio2 ffmpeg
- name: System deps (macOS)
if: runner.os == 'macOS'
run: brew install portaudio ffmpeg
# Use the system interpreter (no virtualenv) so install.sh's `pip --user`
# branch is allowed. Editable install makes `aai_cli` importable for the
# test's __version__ check; uv builds the wheel; pipx drives the pipx branch.
- name: Tooling
run: |
python -m pip install --upgrade pip # need pip >= 25.1 for --group
python -m pip install -e . --group dev uv pipx
- name: Real install smoke
run: python -m pytest -q -m install_script ${{ matrix.kfilter }}