diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml new file mode 100644 index 0000000000..b9e267f31e --- /dev/null +++ b/.github/workflows/release-dry-run.yml @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: Release Dry Run + +on: + workflow_dispatch: + inputs: + tag: + description: 'Version tag to simulate (e.g. v0.1.0)' + required: true + +jobs: + dry-run: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v7 + with: + python-version: '3.12' + - name: Validate tag matches pyproject.toml version + run: | + TAG="${{ inputs.tag }}" + TAG="${TAG#v}" + PKG_VERSION=$(python3 -c " + import tomllib + with open('pyproject.toml', 'rb') as f: + print(tomllib.load(f)['project']['version']) + ") + if [ "$TAG" != "$PKG_VERSION" ]; then + echo "ERROR: input tag v${TAG} does not match pyproject.toml version $PKG_VERSION" + exit 1 + fi + echo "Version validated: $PKG_VERSION" + - name: Build only (no publish) + run: uv build + - name: Verify artifacts + run: | + ls dist/*.whl dist/*.tar.gz + sha256sum dist/*.whl dist/*.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..5a71bde314 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +name: Release + +on: + push: + tags: ['v[0-9]*.[0-9]*.[0-9]*'] + workflow_dispatch: + inputs: + tag: + description: 'Version tag to release (e.g. v0.1.0)' + required: true + +permissions: + contents: write + +concurrency: + group: release-${{ github.repository }}-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.tag.outputs.tag }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Assert clean working tree + run: | + git diff --exit-code + git diff --cached --exit-code + test -z "$(git ls-files --others --exclude-standard)" + + - name: Validate tag matches pyproject.toml version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ inputs.tag }}" + else + TAG="${GITHUB_REF_NAME}" + fi + TAG="${TAG#v}" + PKG_VERSION=$(python3 -c " + import tomllib, sys + with open('pyproject.toml', 'rb') as f: + d = tomllib.load(f) + v = d.get('project', {}).get('version') + if not v: + raise SystemExit('ERROR: missing [project].version in pyproject.toml') + print(v) + ") + if [ "$TAG" != "$PKG_VERSION" ]; then + echo "ERROR: tag v${TAG} does not match pyproject.toml version $PKG_VERSION" + exit 1 + fi + echo "Version validated: $PKG_VERSION" + + - uses: astral-sh/setup-uv@v7 + with: + python-version: '3.12' + + - name: Build wheel + sdist + run: uv build + + - name: Verify dist artifacts exist + run: | + ls -la dist/ + ls dist/*.whl dist/*.tar.gz >/dev/null 2>&1 || { + echo "ERROR: expected wheel + sdist in dist/" + exit 1 + } + + - name: Publish to PyPI + run: | + uv publish --token "$UV_PUBLISH_TOKEN" dist/*.whl dist/*.tar.gz + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} + + - name: Generate checksums + run: sha256sum dist/*.whl dist/*.tar.gz > dist/SHA256SUMS.txt + + - name: Set release tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.tag }} + files: | + dist/*.whl + dist/*.tar.gz + dist/SHA256SUMS.txt + generate_release_notes: true + prerelease: ${{ contains(steps.tag.outputs.tag, 'rc') }}