docs(agents): require DCO signoff on every commit #10
Workflow file for this run
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: Release | |
| # Builds standalone binaries for Linux / macOS / Windows and publishes them | |
| # as assets on the GitHub Release corresponding to the pushed tag. | |
| # | |
| # Trigger: | |
| # - Push a git tag matching v* (e.g. v0.1.0) → builds all targets and | |
| # creates/updates a Release named after the tag. | |
| # - workflow_dispatch → manual run for debugging; requires a tag input | |
| # that already exists on the repository. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Existing tag to build and release (e.g. v0.1.0)' | |
| required: true | |
| permissions: | |
| contents: write | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| DISABLE_BREAKING_CHANGES_WARNING: '1' | |
| jobs: | |
| # --------------------------------------------------------------------- | |
| # Version sanity check: setuptools-scm must resolve the tag to the | |
| # same PEP 440 version we expect from the tag name (v1.2.3 → 1.2.3). | |
| # --------------------------------------------------------------------- | |
| verify-version: | |
| name: Verify version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tag: ${{ steps.resolve.outputs.tag }} | |
| version: ${{ steps.resolve.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| fetch-depth: 0 # setuptools-scm needs full history + tags | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Resolve tag and version | |
| id: resolve | |
| shell: bash | |
| run: | | |
| TAG="${{ github.event.inputs.tag || github.ref_name }}" | |
| VERSION="${TAG#v}" | |
| python -m pip install --upgrade pip | |
| python -m pip install "setuptools-scm>=8" | |
| SCM_VERSION=$(python -m setuptools_scm) | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| if [ "$VERSION" != "$SCM_VERSION" ]; then | |
| echo "::error::Tag version ($VERSION) does not match setuptools-scm resolution ($SCM_VERSION). Make sure the tag points at the commit you want to release." | |
| exit 1 | |
| fi | |
| echo "Building version $VERSION from tag $TAG" | |
| # --------------------------------------------------------------------- | |
| # Matrix build: 5 targets | |
| # --------------------------------------------------------------------- | |
| build: | |
| name: Build ${{ matrix.target }} | |
| needs: verify-version | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Linux builds use ubuntu-22.04 (glibc 2.35) rather than a manylinux | |
| # container: manylinux ships a static CPython without libpython*.so, | |
| # which PyInstaller cannot embed. The glibc-2.35 floor still covers | |
| # Ubuntu 22.04+, Debian 12+, RHEL 9+, and all current modern distros. | |
| - target: linux-amd64 | |
| runner: ubuntu-22.04 | |
| ext: '' | |
| archive: tar.gz | |
| - target: linux-arm64 | |
| runner: ubuntu-22.04-arm | |
| ext: '' | |
| archive: tar.gz | |
| - target: darwin-amd64 | |
| runner: macos-15-intel | |
| ext: '' | |
| archive: tar.gz | |
| - target: darwin-arm64 | |
| runner: macos-latest | |
| ext: '' | |
| archive: tar.gz | |
| - target: windows-amd64 | |
| runner: windows-latest | |
| ext: '.exe' | |
| archive: zip | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.verify-version.outputs.tag }} | |
| fetch-depth: 0 # setuptools-scm needs full history + tags | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install project + PyInstaller | |
| shell: bash | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e . | |
| python -m pip install pyinstaller | |
| - name: Build binary | |
| shell: bash | |
| run: | | |
| pyinstaller --clean --noconfirm agentrun.spec | |
| ls -lh dist/ | |
| - name: Smoke test binary | |
| shell: bash | |
| run: | | |
| ./dist/agentrun${{ matrix.ext }} --version | |
| # --- Package (Unix) ----------------------------------------------- | |
| - name: Package tar.gz (Unix) | |
| if: matrix.archive == 'tar.gz' | |
| shell: bash | |
| run: | | |
| VERSION="${{ needs.verify-version.outputs.version }}" | |
| ASSET="agentrun-${VERSION}-${{ matrix.target }}.tar.gz" | |
| cd dist | |
| tar czf "../${ASSET}" "agentrun${{ matrix.ext }}" | |
| cd .. | |
| shasum -a 256 "${ASSET}" > "${ASSET}.sha256" | |
| echo "ASSET=${ASSET}" >> "$GITHUB_ENV" | |
| # --- Package (Windows) -------------------------------------------- | |
| - name: Package zip (Windows) | |
| if: matrix.archive == 'zip' | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.verify-version.outputs.version }}" | |
| $asset = "agentrun-$version-${{ matrix.target }}.zip" | |
| Compress-Archive -Path "dist/agentrun${{ matrix.ext }}" -DestinationPath $asset -Force | |
| $hash = (Get-FileHash $asset -Algorithm SHA256).Hash.ToLower() | |
| "$hash $asset" | Out-File -Encoding ascii "$asset.sha256" | |
| echo "ASSET=$asset" >> $env:GITHUB_ENV | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: agentrun-${{ matrix.target }} | |
| path: | | |
| ${{ env.ASSET }} | |
| ${{ env.ASSET }}.sha256 | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # --------------------------------------------------------------------- | |
| # Collect all artifacts and publish the Release | |
| # --------------------------------------------------------------------- | |
| release: | |
| name: Publish Release | |
| needs: [verify-version, build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| pattern: agentrun-* | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: ls -lh dist/ | |
| - name: Create combined checksums file | |
| working-directory: dist | |
| run: | | |
| cat *.sha256 > SHA256SUMS | |
| echo "---" | |
| cat SHA256SUMS | |
| - name: Publish to GitHub Releases | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.verify-version.outputs.tag }} | |
| name: ${{ needs.verify-version.outputs.tag }} | |
| draft: false | |
| # PEP 440 pre-release markers: aN / bN / rcN / .devN. Tags must be in | |
| # canonical form (no '-'), so we match on these substrings instead. | |
| prerelease: ${{ contains(needs.verify-version.outputs.version, 'a') || contains(needs.verify-version.outputs.version, 'b') || contains(needs.verify-version.outputs.version, 'rc') || contains(needs.verify-version.outputs.version, 'dev') }} | |
| generate_release_notes: true | |
| files: | | |
| dist/agentrun-*.tar.gz | |
| dist/agentrun-*.zip | |
| dist/agentrun-*.sha256 | |
| dist/SHA256SUMS | |
| # --------------------------------------------------------------------- | |
| # Build sdist + wheel for PyPI. Runs in parallel with the binary build | |
| # matrix; decoupled so a PyPI outage does not block the GitHub Release | |
| # (and vice versa). | |
| # --------------------------------------------------------------------- | |
| build-dist: | |
| name: Build sdist + wheel | |
| needs: verify-version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.verify-version.outputs.tag }} | |
| fetch-depth: 0 # setuptools-scm needs full history + tags | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Build sdist + wheel | |
| run: | | |
| python -m pip install --upgrade pip build | |
| python -m build --sdist --wheel --outdir dist-pypi | |
| ls -lh dist-pypi/ | |
| - name: Verify built artifacts carry the tag version | |
| run: | | |
| VERSION="${{ needs.verify-version.outputs.version }}" | |
| # Filenames follow PEP 440 / wheel spec: agentrun_cli-<version>-py3-none-any.whl | |
| # and agentrun_cli-<version>.tar.gz. Bail out if the version string is absent. | |
| if ! ls dist-pypi/ | grep -q "agentrun_cli-${VERSION}"; then | |
| echo "::error::Built artifacts in dist-pypi/ do not contain version ${VERSION}:" | |
| ls dist-pypi/ | |
| exit 1 | |
| fi | |
| - name: Check metadata with twine | |
| run: | | |
| python -m pip install twine | |
| python -m twine check dist-pypi/* | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist-pypi/* | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # --------------------------------------------------------------------- | |
| # Publish sdist + wheel to PyPI. | |
| # | |
| # Auth: uses the PYPI_API_TOKEN repository secret. The secret must be a | |
| # PyPI project-scoped token (account → Add API token → scope: | |
| # "agentrun-cli"). Add it at Settings → Secrets and variables → Actions. | |
| # | |
| # Hardening (recommended once the first release lands): | |
| # - Configure the `pypi` environment with required reviewers, so every | |
| # publish requires a manual approval. | |
| # - Migrate to PyPI trusted publishing (OIDC): drop the token, add | |
| # `id-token: write` to permissions, replace the `password:` input | |
| # with no input at all. See: https://docs.pypi.org/trusted-publishers/ | |
| # --------------------------------------------------------------------- | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: [verify-version, build-dist] | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/agentrun-cli | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist-pypi | |
| - name: List artifacts to upload | |
| run: ls -lh dist-pypi/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist-pypi | |
| password: ${{ secrets.PYPI_API_TOKEN }} | |
| # Fail loudly if the version already exists on PyPI; re-uploading | |
| # is prevented by PyPI anyway, and silent skips mask tag reuse. | |
| skip-existing: false | |
| verify-metadata: true |