Bump to 1.0.12 and drop the README PyPI version badge. #31
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
| name: CI | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: Overwrite an existing GitHub release tag vX.Y.Z | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| quality: | |
| name: Static analysis and formatting | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Run quality checks | |
| run: | | |
| python scripts/generate_spec_data.py --check | |
| python -m black --check src tests scripts | |
| python -m isort --check-only src tests scripts | |
| python -m autopep8 -r --diff --exit-code --select=W291,W293 src tests scripts | |
| python -m mypy src/battest tests scripts | |
| python -m pylint src/battest scripts | |
| python -m pyproject_fmt --check pyproject.toml | |
| python -m bandit -r src/battest -c pyproject.toml -ll -q | |
| python -m blinter scripts --no-recursive --config scripts/blinter.ini | |
| python -m blinter examples | |
| python -m pip_audit | |
| test-unit: | |
| name: pytest unit (${{ matrix.os }}, Python ${{ matrix.python-version }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| python-version: ["3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Copy spec catalogs | |
| run: python scripts/generate_spec_data.py | |
| - name: Run unit tests | |
| run: python -m pytest -m "not windows" --cov-fail-under=0 | |
| test-windows: | |
| name: pytest windows (${{ matrix.python-version }}) | |
| runs-on: windows-latest | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: "1.97.1" | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Copy spec catalogs | |
| run: python scripts/generate_spec_data.py | |
| - name: Run example suites with committed stub | |
| run: python -m battest run examples --safe-defaults | |
| - name: Rebuild PATH-mock stub | |
| run: python scripts/build_stub.py | |
| - name: Run full tests | |
| run: python -m pytest | |
| - name: Check line, branch, function, and class coverage | |
| run: python scripts/check_coverage.py | |
| - name: Run example suites with rebuilt stub | |
| run: python -m battest run examples --safe-defaults | |
| action-windows: | |
| name: Composite action dogfood | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| - id: battest | |
| uses: ./ | |
| with: | |
| path: examples | |
| extra-args: '["--jobs","1"]' | |
| safe-defaults: "true" | |
| - name: Assert JUnit report exists | |
| shell: pwsh | |
| env: | |
| BATTEST_JUNIT: ${{ steps.battest.outputs.junit-xml }} | |
| run: | | |
| if (-not (Test-Path -LiteralPath $env:BATTEST_JUNIT)) { | |
| throw "junit-xml output is missing: $env:BATTEST_JUNIT" | |
| } | |
| if ((Get-Item -LiteralPath $env:BATTEST_JUNIT).Length -lt 1) { | |
| throw "junit-xml is empty: $env:BATTEST_JUNIT" | |
| } | |
| - name: Dogfood missing discovery path | |
| id: battest-fail | |
| shell: pwsh | |
| env: | |
| BATTEST_PATH: missing-battest-fixtures | |
| BATTEST_EXTRA_ARGS: '["--jobs","1"]' | |
| BATTEST_SAFE_DEFAULTS: "true" | |
| run: | | |
| # Dot-source so a non-zero battest exit is a return value, not | |
| # a step failure / ##[error] annotation. | |
| $PSNativeCommandUseErrorActionPreference = $false | |
| . "$env:GITHUB_WORKSPACE/scripts/run-battest-action.ps1" | |
| $code = Invoke-BattestAction | |
| if ($code -eq 0) { | |
| throw "expected battest to fail when the discovery path is missing" | |
| } | |
| # pwsh -Command exits with the last native command's code otherwise. | |
| exit 0 | |
| - name: Assert JUnit written on discovery failure | |
| shell: pwsh | |
| env: | |
| BATTEST_JUNIT: ${{ steps.battest-fail.outputs.junit-xml }} | |
| run: | | |
| if (-not (Test-Path -LiteralPath $env:BATTEST_JUNIT)) { | |
| throw "junit-xml output is missing: $env:BATTEST_JUNIT" | |
| } | |
| if ((Get-Item -LiteralPath $env:BATTEST_JUNIT).Length -lt 1) { | |
| throw "junit-xml is empty: $env:BATTEST_JUNIT" | |
| } | |
| $text = Get-Content -LiteralPath $env:BATTEST_JUNIT -Raw | |
| if ($text -notmatch 'does not exist') { | |
| throw "junit-xml does not record the missing discovery path" | |
| } | |
| - name: Analyze PowerShell action script | |
| shell: pwsh | |
| run: | | |
| & ./scripts/check_action_ps1.ps1 | |
| codeql: | |
| name: CodeQL (${{ matrix.language }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| actions: read | |
| contents: read | |
| packages: read | |
| security-events: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - language: python | |
| build-mode: none | |
| - language: actions | |
| build-mode: none | |
| - language: rust | |
| build-mode: none | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| fetch-depth: 2 | |
| - name: Set up Rust | |
| if: matrix.language == 'rust' | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: "1.97.1" | |
| - name: Fetch Rust crate graph | |
| if: matrix.language == 'rust' | |
| run: cargo fetch --locked --manifest-path stub/Cargo.toml | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@v4 | |
| with: | |
| languages: ${{ matrix.language }} | |
| build-mode: ${{ matrix.build-mode }} | |
| - name: Perform CodeQL analysis | |
| uses: github/codeql-action/analyze@v4 | |
| with: | |
| category: "/language:${{ matrix.language }}" | |
| rust-quality: | |
| name: Rust format, check, clippy, coverage, audit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: "1.97.1" | |
| components: rustfmt, clippy, llvm-tools-preview | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: stub | |
| - uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-llvm-cov,cargo-audit | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| - name: Install coverage gate dependencies | |
| run: python -m pip install pydantic | |
| - name: rustfmt | |
| run: cargo fmt --all --check --manifest-path stub/Cargo.toml | |
| - name: cargo check | |
| run: cargo check --manifest-path stub/Cargo.toml --all-targets --locked | |
| - name: clippy | |
| run: > | |
| cargo clippy --manifest-path stub/Cargo.toml --all-targets --locked | |
| -- -D warnings | |
| - name: coverage | |
| run: python scripts/check_rust_coverage.py | |
| - name: cargo audit | |
| run: cargo audit --file stub/Cargo.lock | |
| rust-test: | |
| name: cargo test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: "1.97.1" | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: stub | |
| - name: cargo check | |
| run: cargo check --manifest-path stub/Cargo.toml --all-targets --locked | |
| - name: cargo test | |
| run: cargo test --manifest-path stub/Cargo.toml --locked --all-targets | |
| test-windows-exe: | |
| name: Windows executable smoke tests | |
| runs-on: windows-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| - name: Create virtual environment | |
| run: python -m venv venv | |
| - name: Install dependencies in virtual environment | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python -m pip install --upgrade pip | |
| python -m pip install --upgrade setuptools | |
| python -m pip install --upgrade wheel | |
| python -m pip install --upgrade pyinstaller | |
| python -m pip install --upgrade -r requirements.txt | |
| shell: pwsh | |
| - name: Generate version info file | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python scripts/generate_file_version_info.py | |
| shell: pwsh | |
| - name: Build executable | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| pyinstaller battest.spec | |
| shell: pwsh | |
| - name: Verify executable exists | |
| run: | | |
| if (-not (Test-Path -Path "dist\battest.exe" -PathType Leaf)) { | |
| throw "dist\battest.exe was not produced by PyInstaller" | |
| } | |
| shell: pwsh | |
| - name: Install battest for parity checks | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python -m pip install -e . | |
| shell: pwsh | |
| - name: Smoke test executable | |
| run: .\scripts\test_exe_smoke.ps1 -ExePath .\dist\battest.exe | |
| shell: pwsh | |
| package-smoke: | |
| name: sdist and wheel | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| - name: Install build tools | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install --upgrade build twine | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| - name: Check dist | |
| run: python -m twine check dist/* | |
| - name: Install wheel and import battest | |
| run: | | |
| python -m pip install dist/*.whl | |
| python -c " | |
| import importlib.metadata | |
| from importlib.resources import files | |
| import battest | |
| from battest.mocks import stub_executable | |
| assert battest.__version__ | |
| scripts = importlib.metadata.entry_points(group='console_scripts') | |
| assert any(item.name == 'battest' for item in scripts) | |
| assert files('battest').joinpath('data/battest_stub.exe').is_file() | |
| assert files('battest').joinpath('data/commands.yaml').is_file() | |
| assert files('battest').joinpath('data/expansion.yaml').is_file() | |
| assert files('battest').joinpath('data/battest-expect.schema.json').is_file() | |
| assert stub_executable().is_file() | |
| " | |
| dependency-graph: | |
| name: Dependency Graph | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Submit dependency snapshot | |
| uses: advanced-security/component-detection-dependency-submission-action@v0.1.4 | |
| with: | |
| detectorArgs: Pip=EnableIfDefaultOff | |
| check-version: | |
| name: Check pyproject.toml version bump | |
| needs: | |
| - quality | |
| - test-unit | |
| - test-windows | |
| - action-windows | |
| - codeql | |
| - rust-quality | |
| - rust-test | |
| - test-windows-exe | |
| - package-smoke | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| concurrency: | |
| group: battest-release | |
| cancel-in-progress: false | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| should_publish_pypi: ${{ steps.check.outputs.should_publish_pypi }} | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 2 | |
| fetch-tags: true | |
| persist-credentials: false | |
| - name: Read version from pyproject.toml | |
| id: version | |
| run: | | |
| VERSION=$(python -c "import pathlib, tomllib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text(encoding='utf-8'))['project']['version'])") | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Project version: $VERSION" | |
| - name: Decide whether to release | |
| id: check | |
| run: | | |
| NEW_VERSION="${{ steps.version.outputs.version }}" | |
| FORCE="${{ github.event.inputs.force }}" | |
| TAG_EXISTS=false | |
| if git rev-parse --verify --quiet "refs/tags/v${NEW_VERSION}"; then | |
| TAG_EXISTS=true | |
| fi | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| if [ "$TAG_EXISTS" = true ] && [ "$FORCE" != "true" ]; then | |
| echo "Manual dispatch without force; tag v${NEW_VERSION} exists." | |
| echo "Skipping GitHub release rewrite and PyPI." | |
| echo "should_release=false" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Manual workflow_dispatch; releasing version ${NEW_VERSION}." | |
| echo "should_release=true" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| OLD_VERSION=$(python scripts/read_git_pyproject_version.py HEAD^) | |
| if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then | |
| echo "Version bumped: $OLD_VERSION -> $NEW_VERSION" | |
| echo "should_release=true" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$TAG_EXISTS" = true ]; then | |
| echo "Version unchanged ($NEW_VERSION) and tag v${NEW_VERSION} exists." | |
| echo "Skipping GitHub assets and PyPI." | |
| echo "should_release=false" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Version unchanged ($NEW_VERSION) but tag v${NEW_VERSION} is missing; releasing." | |
| echo "should_release=true" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| build-windows: | |
| needs: [check-version] | |
| if: needs.check-version.outputs.should_release == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Read version from pyproject.toml | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $match = Select-String -Path "pyproject.toml" -Pattern '^version = "(.+)"' | |
| if (-not $match) { throw "Could not read version from pyproject.toml" } | |
| $version = $match.Matches[0].Groups[1].Value | |
| "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| - name: Create virtual environment | |
| run: python -m venv venv | |
| - name: Install dependencies in virtual environment | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python -m pip install --upgrade pip | |
| python -m pip install --upgrade setuptools | |
| python -m pip install --upgrade wheel | |
| python -m pip install --upgrade pyinstaller | |
| python -m pip install --upgrade -r requirements.txt | |
| shell: pwsh | |
| - name: Generate version info file | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python scripts/generate_file_version_info.py | |
| shell: pwsh | |
| - name: Build executable | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| pyinstaller battest.spec | |
| shell: pwsh | |
| - name: Install battest for parity checks | |
| run: | | |
| .\venv\Scripts\Activate.ps1 | |
| python -m pip install -e . | |
| shell: pwsh | |
| - name: Smoke test executable | |
| run: .\scripts\test_exe_smoke.ps1 -ExePath .\dist\battest.exe | |
| shell: pwsh | |
| - name: Package executable for release | |
| run: | | |
| $version = "${{ steps.version.outputs.version }}" | |
| $folder = "dist/Battest-v$version" | |
| New-Item -ItemType Directory -Path $folder -Force | |
| Move-Item -Path dist/battest.exe -Destination "$folder/battest.exe" | |
| Compress-Archive -Path $folder -DestinationPath "dist/Battest-v$version.zip" | |
| shell: pwsh | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: windows-build | |
| path: dist/Battest-v${{ steps.version.outputs.version }}.zip | |
| build-wheels: | |
| name: Build Python wheels | |
| needs: [check-version] | |
| if: needs.check-version.outputs.should_publish_pypi == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install --upgrade build | |
| - name: Build pure Python wheel and sdist | |
| run: python -m build | |
| - name: Store artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: python-dist | |
| path: dist/ | |
| create-release: | |
| needs: [check-version, build-windows, build-wheels] | |
| if: needs.check-version.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Download Windows build | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: windows-build | |
| path: ./build | |
| - name: Extract release notes | |
| id: release_notes | |
| run: | | |
| NOTES_FILE="${RUNNER_TEMP}/release-notes.md" | |
| python scripts/extract_release_notes.py > "$NOTES_FILE" | |
| echo "path=$NOTES_FILE" >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| artifacts: ./build/Battest-v${{ needs.check-version.outputs.version }}.zip | |
| tag: v${{ needs.check-version.outputs.version }} | |
| bodyFile: ${{ steps.release_notes.outputs.path }} | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| allowUpdates: ${{ github.event.inputs.force == 'true' }} | |
| removeArtifacts: ${{ github.event.inputs.force == 'true' }} | |
| - name: Move major version tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ needs.check-version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.+-].*)?$'; then | |
| echo "unexpected version: $VERSION" >&2 | |
| exit 1 | |
| fi | |
| MAJOR="${VERSION%%.*}" | |
| SHA="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/v${VERSION}" --jq '.object.sha')" | |
| TYPE="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/v${VERSION}" --jq '.object.type')" | |
| if [ "$TYPE" = "tag" ]; then | |
| SHA="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${SHA}" --jq '.object.sha')" | |
| fi | |
| echo "Pointing v${MAJOR} at v${VERSION} (${SHA})" | |
| if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/v${MAJOR}" >/dev/null 2>&1; then | |
| gh api --method PATCH "repos/${GITHUB_REPOSITORY}/git/refs/tags/v${MAJOR}" \ | |
| -f sha="$SHA" -F force=true | |
| else | |
| gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \ | |
| -f ref="refs/tags/v${MAJOR}" -f sha="$SHA" | |
| fi | |
| publish-pypi: | |
| name: Publish wheels to PyPI | |
| needs: [check-version, build-wheels, create-release] | |
| if: needs.create-release.result == 'success' | |
| environment: pypi | |
| permissions: | |
| id-token: write | |
| contents: read | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: python-dist | |
| path: dist | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| - name: Publish to PyPI | |
| env: | |
| TWINE_USERNAME: __token__ | |
| TWINE_PASSWORD: ${{ secrets.PYPI_BATTEST }} | |
| run: | | |
| python -m pip install --upgrade twine | |
| python -m twine check dist/* | |
| python -m twine upload dist/* |