fix(rg): cap passthru replacement matches #2990
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # CI for bashkit Python package | |
| # Builds the native extension via maturin and runs pytest on each PR. | |
| # Complements publish-python.yml (release-only) with per-PR validation. | |
| name: Python | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "crates/bashkit-python/**" | |
| - "crates/bashkit/**" | |
| - "examples/*.py" | |
| - "examples/*.ipynb" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| - ".github/workflows/python.yml" | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "crates/bashkit-python/**" | |
| - "crates/bashkit/**" | |
| - "examples/*.py" | |
| - "examples/*.ipynb" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| - ".github/workflows/python.yml" | |
| workflow_call: | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| MATURIN_VERSION: ">=1.4,<2.0" | |
| jobs: | |
| lint: | |
| name: Lint & Format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| - name: Ruff check | |
| run: uvx ruff check crates/bashkit-python | |
| - name: Ruff format | |
| run: uvx ruff format --check crates/bashkit-python | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Mypy type check | |
| run: | | |
| pip install mypy | |
| mypy crates/bashkit-python/bashkit/ --ignore-missing-imports | |
| test: | |
| name: Test (Python ${{ matrix.python-version }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.9", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install maturin | |
| run: python -m pip install "maturin${MATURIN_VERSION}" | |
| - name: Build wheel | |
| working-directory: crates/bashkit-python | |
| run: python -m maturin build --release --out dist -i python${{ matrix.python-version }} | |
| - name: Install wheel and test dependencies | |
| run: | | |
| pip install bashkit --no-index --find-links crates/bashkit-python/dist --force-reinstall | |
| pip install pytest pytest-asyncio langchain-core langgraph fastapi httpx maturin | |
| - name: Build random filesystem fixture | |
| run: | | |
| python -m maturin build --release --out /tmp/bashkit-random-fs \ | |
| -m crates/bashkit-python/test-fixtures/random-fs/Cargo.toml \ | |
| -i python${{ matrix.python-version }} | |
| pip install bashkit-random-fs --no-index --find-links /tmp/bashkit-random-fs --force-reinstall | |
| - name: Run tests | |
| working-directory: crates/bashkit-python | |
| run: pytest tests/ -v | |
| examples: | |
| name: Examples | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install maturin | |
| run: python -m pip install "maturin${MATURIN_VERSION}" | |
| - name: Build wheel | |
| working-directory: crates/bashkit-python | |
| run: python -m maturin build --release --out dist -i python3.12 | |
| - name: Install local wheel | |
| run: | | |
| pip install bashkit --no-index --find-links crates/bashkit-python/dist --force-reinstall | |
| pip install langchain-core langgraph fastapi httpx uvicorn nbconvert jupyter-client ipykernel maturin | |
| - name: Build random filesystem fixture | |
| run: | | |
| python -m maturin build --release --out /tmp/bashkit-random-fs \ | |
| -m crates/bashkit-python/test-fixtures/random-fs/Cargo.toml \ | |
| -i python3.12 | |
| pip install bashkit-random-fs --no-index --find-links /tmp/bashkit-random-fs --force-reinstall | |
| - name: Run examples | |
| run: | | |
| python crates/bashkit-python/examples/bash_basics.py | |
| python crates/bashkit-python/examples/custom_filesystem_interop.py | |
| python crates/bashkit-python/examples/k8s_orchestrator.py | |
| python crates/bashkit-python/examples/data_pipeline.py | |
| python crates/bashkit-python/examples/llm_tool.py | |
| python crates/bashkit-python/examples/langgraph_async_tool.py | |
| python crates/bashkit-python/examples/fastapi_async_tool.py | |
| - name: Run notebooks | |
| run: | | |
| jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=120 \ | |
| examples/jupyter_basics.ipynb --output /tmp/jupyter_basics_out.ipynb | |
| # Verify wheel builds and passes twine check | |
| build-wheel: | |
| name: Build wheel | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install maturin | |
| run: python -m pip install "maturin${MATURIN_VERSION}" | |
| - name: Build wheel | |
| working-directory: crates/bashkit-python | |
| run: python -m maturin build --release --out dist | |
| - name: Verify wheel metadata | |
| run: | | |
| pip install twine | |
| twine check crates/bashkit-python/dist/* | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: python-wheel | |
| path: crates/bashkit-python/dist | |
| retention-days: 5 | |
| # Build the reduced-feature Pyodide/Emscripten wheel and smoke-test it in a | |
| # Pyodide venv. See specs/emscripten-wheels.md for the feature matrix and the | |
| # pyodide-build <-> Emscripten <-> Python <-> Rust version lockstep. | |
| wasm: | |
| name: Build wheel (Pyodide/Emscripten) | |
| runs-on: ubuntu-latest | |
| env: | |
| # Pinned for reproducibility: this trio must stay aligned on the wasm | |
| # feature set + exception-handling ABI (see specs/emscripten-wheels.md | |
| # "version triangle"). pyodide-build 0.34.x (under Python 3.13) selects | |
| # Pyodide 0.29.x / Emscripten 4.0.9; the nightly's LLVM must match that | |
| # binaryen. Bump all three together and re-verify the wheel imports. | |
| RUST_NIGHTLY: "nightly-2026-05-29" | |
| PYODIDE_BUILD_VERSION: "0.34.4" | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| # Python 3.13 selects pyodide-build's modern config (Pyodide 0.29.x, | |
| # Emscripten 4.0.9), whose binaryen understands the wasm target-features | |
| # modern LLVM emits and whose runtime supports wasm exception handling. | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.13" | |
| # Pyodide passes `-Z link-native-libraries=no` (nightly-only). The nightly | |
| # must satisfy our deps' MSRV (monty needs rustc 1.95) and edition 2024, | |
| # so it has to be recent; its LLVM (19+) matches Emscripten 4.0.9's | |
| # binaryen, which is what avoids the wasm-opt target-feature skew that | |
| # older Emscripten (3.1.x) hit. See specs/emscripten-wheels.md. | |
| - name: Install nightly Rust with the Emscripten target | |
| # @nightly matches the repo's other nightly jobs (fuzz.yml, nightly.yml, | |
| # ci.yml); the exact nightly is pinned via the toolchain: input below. | |
| uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly | |
| with: | |
| toolchain: ${{ env.RUST_NIGHTLY }} | |
| targets: wasm32-unknown-emscripten | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| # pyodide-build manages its own matching emsdk via the cross-build env, so | |
| # no separate setup-emsdk step is needed. | |
| - name: Install pyodide-build | |
| run: pip install "pyodide-build==${PYODIDE_BUILD_VERSION}" | |
| - name: Install pyodide cross-build environment | |
| run: pyodide xbuildenv install | |
| - name: Build Pyodide wheel | |
| working-directory: crates/bashkit-python | |
| env: | |
| RUSTUP_TOOLCHAIN: ${{ env.RUST_NIGHTLY }} | |
| run: pyodide build --outdir dist | |
| - name: Smoke test in a Pyodide venv | |
| working-directory: crates/bashkit-python | |
| # Run the import test from a scratch dir: the crate's own `bashkit/` | |
| # source package would otherwise shadow the installed extension module. | |
| run: | | |
| pyodide venv .venv-pyodide | |
| .venv-pyodide/bin/pip install dist/*.whl | |
| venv_python="$(pwd)/.venv-pyodide/bin/python" | |
| cd "$(mktemp -d)" | |
| "$venv_python" -c " | |
| from bashkit import Bash | |
| b = Bash(python=True) | |
| r = b.execute_sync('echo hello && echo 1 | jq .') | |
| print(r.stdout) | |
| assert r.exit_code == 0, r | |
| assert r.stdout == 'hello\n1\n', r.stdout | |
| # Reduced-feature build: unavailable config must fail loudly. | |
| try: | |
| Bash(sqlite=True) | |
| except RuntimeError: | |
| pass | |
| else: | |
| raise AssertionError('expected sqlite=True to raise on wasm') | |
| print('wasm smoke test OK') | |
| " | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: python-wheel-emscripten | |
| path: crates/bashkit-python/dist | |
| retention-days: 5 | |
| # Gate job for branch protection | |
| python-check: | |
| name: Python Check | |
| if: always() | |
| needs: [lint, test, examples, build-wheel, wasm] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify all jobs passed | |
| run: | | |
| if [[ "${{ needs.lint.result }}" != "success" ]] || \ | |
| [[ "${{ needs.test.result }}" != "success" ]] || \ | |
| [[ "${{ needs.examples.result }}" != "success" ]] || \ | |
| [[ "${{ needs.build-wheel.result }}" != "success" ]] || \ | |
| [[ "${{ needs.wasm.result }}" != "success" ]]; then | |
| echo "One or more Python CI jobs failed" | |
| exit 1 | |
| fi |