Release #21
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
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Release Workflow — agentomatic | |
| # | |
| # Semantic release → Bundle Studio UI → Build + Publish to PyPI → GitHub | |
| # Release → Deploy docs. The bundle-studio step downloads the latest React | |
| # UI build from agentomatic-studio releases and places it in | |
| # src/agentomatic/studio/static/ so `pip install agentomatic[studio]` ships | |
| # the frontend. | |
| # | |
| # Uses GitHub App (TECHNICAL_APP_APP_ID + TECHNICAL_APP_PEM) for cross-repo | |
| # access to the private agentomatic-studio repository. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| name: Release | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| DRY_RUN: | |
| required: false | |
| default: false | |
| type: boolean | |
| description: "Run in dry-run mode (no actual release)" | |
| # Allow agentomatic-studio to trigger a rebuild after a new UI release | |
| repository_dispatch: | |
| types: [studio-release] | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| jobs: | |
| # ───────────────────────────────────────────────────────────── | |
| # 1. SEMANTIC RELEASE — Bump version, tag, create GitHub release | |
| # ───────────────────────────────────────────────────────────── | |
| semantic-release: | |
| name: Semantic Release | |
| runs-on: ubuntu-latest | |
| concurrency: release | |
| if: github.repository_owner == 'UnicoLab' | |
| outputs: | |
| new_version: ${{ steps.release.outputs.version }} | |
| released: ${{ steps.release.outputs.released }} | |
| steps: | |
| - name: Generate App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.TECHNICAL_APP_APP_ID }} | |
| private-key: ${{ secrets.TECHNICAL_APP_PEM }} | |
| owner: UnicoLab | |
| repositories: agentomatic,agentomatic-studio | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| - name: Install dependencies | |
| run: uv sync --group dev | |
| - name: Python Semantic Release | |
| id: release | |
| uses: python-semantic-release/python-semantic-release@v9 | |
| with: | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| root_options: ${{ inputs.DRY_RUN && '--noop' || '' }} | |
| - name: Show release info | |
| run: | | |
| echo "Released: ${{ steps.release.outputs.released }}" | |
| echo "Version: ${{ steps.release.outputs.version }}" | |
| - name: Save recovery version | |
| if: steps.release.outputs.released == 'true' | |
| run: | | |
| mkdir -p .github/recovery | |
| echo "${{ steps.release.outputs.version }}" > .github/recovery/last_release_version.txt | |
| # ───────────────────────────────────────────────────────────── | |
| # 2. BUNDLE STUDIO — Download latest Studio UI build from | |
| # agentomatic-studio releases and stage for the wheel build. | |
| # ───────────────────────────────────────────────────────────── | |
| bundle-studio: | |
| name: Bundle Studio UI | |
| needs: [semantic-release] | |
| if: needs.semantic-release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.TECHNICAL_APP_APP_ID }} | |
| private-key: ${{ secrets.TECHNICAL_APP_PEM }} | |
| owner: UnicoLab | |
| repositories: agentomatic-studio | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 1 | |
| # Download the latest Studio UI tarball from the private | |
| # agentomatic-studio repo. | |
| - name: Download latest studio release | |
| id: download | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| echo "⬇️ Downloading latest Studio UI release..." | |
| gh release download \ | |
| --repo UnicoLab/agentomatic-studio \ | |
| --pattern "studio-ui-*.tar.gz" \ | |
| --dir /tmp/studio-download | |
| TARBALL=$(ls /tmp/studio-download/studio-ui-*.tar.gz) | |
| echo "tarball=${TARBALL}" >> "$GITHUB_OUTPUT" | |
| # Extract version from filename | |
| STUDIO_VERSION=$(basename "$TARBALL" | sed 's/studio-ui-//; s/.tar.gz//') | |
| echo "studio_version=${STUDIO_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "📦 Studio UI version: ${STUDIO_VERSION}" | |
| - name: Extract studio build | |
| run: | | |
| STATIC_DIR="src/agentomatic/studio/static" | |
| # Clean previous files (keep .gitignore) | |
| find "${STATIC_DIR}" -mindepth 1 ! -name '.gitignore' -delete 2>/dev/null || true | |
| # Extract tarball — build contents are at root level | |
| echo "📂 Extracting to ${STATIC_DIR}/" | |
| tar -xzf "${{ steps.download.outputs.tarball }}" -C "${STATIC_DIR}" | |
| # Verify | |
| if [ ! -f "${STATIC_DIR}/index.html" ]; then | |
| echo "❌ ERROR: index.html not found!" | |
| exit 1 | |
| fi | |
| echo "✅ Studio UI v${{ steps.download.outputs.studio_version }} extracted" | |
| echo "📊 Files: $(find "${STATIC_DIR}" -type f | wc -l)" | |
| # Upload as artifact so pypi-publish can access the files | |
| - name: Upload studio-bundled source | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: source-with-studio | |
| path: . | |
| retention-days: 1 | |
| # ───────────────────────────────────────────────────────────── | |
| # 3. PYPI PUBLISH — Build and publish to PyPI | |
| # (now includes bundled Studio UI static files) | |
| # ───────────────────────────────────────────────────────────── | |
| pypi-publish: | |
| name: Publish to PyPI | |
| needs: [semantic-release, bundle-studio] | |
| if: needs.semantic-release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Download the source tree that already has studio files in place | |
| - name: Download studio-bundled source | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: source-with-studio | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| - name: Verify studio files are present | |
| run: | | |
| STATIC_DIR="src/agentomatic/studio/static" | |
| if [ -f "${STATIC_DIR}/index.html" ]; then | |
| echo "✅ Studio UI is bundled ($(find ${STATIC_DIR} -type f | wc -l) files)" | |
| else | |
| echo "⚠️ Studio UI not found — wheel will ship without frontend" | |
| fi | |
| - name: Build package | |
| run: uv build | |
| - name: Inspect wheel contents | |
| run: | | |
| echo "📦 Wheel contents (studio static files):" | |
| python -m zipfile -l dist/*.whl | grep "studio/static" | head -20 || echo "(no studio files found)" | |
| - name: Publish to PyPI | |
| id: publish | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| password: ${{ secrets.PYPI_TOKEN }} | |
| attestations: false | |
| continue-on-error: true | |
| - name: Retry PyPI publish on failure | |
| if: steps.publish.outcome == 'failure' | |
| run: | | |
| echo "First attempt failed, retrying..." | |
| sleep 10 | |
| rm -rf dist/ | |
| uv build | |
| # Second attempt | |
| - name: Retry publish | |
| if: steps.publish.outcome == 'failure' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| password: ${{ secrets.PYPI_TOKEN }} | |
| attestations: false | |
| # ───────────────────────────────────────────────────────────── | |
| # 4. GITHUB RELEASE — Create GitHub release with assets | |
| # ───────────────────────────────────────────────────────────── | |
| github-release: | |
| name: Publish GitHub Release | |
| needs: [semantic-release] | |
| if: needs.semantic-release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: main | |
| - name: Publish GitHub Release | |
| uses: python-semantic-release/publish-action@v9 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| # ───────────────────────────────────────────────────────────── | |
| # 5. DEPLOY DOCS — Build and deploy versioned documentation | |
| # ───────────────────────────────────────────────────────────── | |
| deploy-docs: | |
| name: Deploy Release Docs | |
| needs: [semantic-release] | |
| if: needs.semantic-release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: deploy-docs | |
| cancel-in-progress: false | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| - name: Install docs dependencies | |
| run: uv sync --group docs | |
| - name: Configure git | |
| run: | | |
| git config user.name "${{ github.actor }}" | |
| git config user.email "${{ github.actor }}@users.noreply.github.com" | |
| - name: Deploy versioned docs | |
| id: deploy_docs | |
| run: | | |
| VERSION="${{ needs.semantic-release.outputs.new_version }}" | |
| echo "Deploying documentation for version $VERSION" | |
| git fetch origin gh-pages:gh-pages --force || echo "No gh-pages branch exists yet — mike will create it" | |
| uv run mike deploy --push --update-aliases --ignore-remote-status "$VERSION" latest | |
| uv run mike set-default --push --ignore-remote-status latest | |
| echo "✓ Deployed docs v${VERSION} (aliased as 'latest', set as default)" |