v3.19.4 #32
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
| # This workflow is triggered when a GitHub release is created. | |
| # It can also be run manually to re-publish to PyPI in case it failed for some reason. | |
| name: Publish PyPI | |
| on: | |
| workflow_dispatch: | |
| inputs: {} | |
| release: | |
| types: [published] | |
| jobs: | |
| build_wheels: | |
| name: build wheels (${{ matrix.binary_name }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| binary_name: stagehand-server-v3-linux-x64 | |
| output_path: src/stagehand/_sea/stagehand-linux-x64 | |
| wheel_platform_tag: manylinux2014_x86_64 | |
| - os: macos-latest | |
| binary_name: stagehand-server-v3-darwin-arm64 | |
| output_path: src/stagehand/_sea/stagehand-darwin-arm64 | |
| wheel_platform_tag: macosx_11_0_arm64 | |
| - os: macos-15-intel | |
| binary_name: stagehand-server-v3-darwin-x64 | |
| output_path: src/stagehand/_sea/stagehand-darwin-x64 | |
| wheel_platform_tag: macosx_10_9_x86_64 | |
| - os: windows-latest | |
| binary_name: stagehand-server-v3-win32-x64.exe | |
| output_path: src/stagehand/_sea/stagehand-win32-x64.exe | |
| wheel_platform_tag: win_amd64 | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| version: "0.9.13" | |
| - name: Resolve latest stagehand/server-v3 release | |
| id: stagehand-server-release | |
| uses: actions/github-script@v6 | |
| with: | |
| github-token: ${{ secrets.STAGEHAND_SOURCE_TOKEN || github.token }} | |
| script: | | |
| const { data } = await github.rest.repos.listReleases({ | |
| owner: 'browserbase', | |
| repo: 'stagehand', | |
| per_page: 100, | |
| }); | |
| const parseStableTag = (tag) => { | |
| if (typeof tag !== 'string') return null; | |
| const match = /^stagehand-server-v3\/v(\d+)\.(\d+)\.(\d+)$/.exec(tag); | |
| if (!match) return null; | |
| return match.slice(1).map(Number); | |
| }; | |
| let best = null; | |
| for (const release of data) { | |
| if (release.draft || release.prerelease) continue; | |
| const version = parseStableTag(release.tag_name); | |
| if (!version) continue; | |
| if (!best) { | |
| best = { release, version }; | |
| continue; | |
| } | |
| const isGreater = | |
| version[0] > best.version[0] || | |
| (version[0] === best.version[0] && version[1] > best.version[1]) || | |
| (version[0] === best.version[0] && version[1] === best.version[1] && version[2] > best.version[2]); | |
| if (isGreater) { | |
| best = { release, version }; | |
| } | |
| } | |
| if (!best) { | |
| core.setFailed('No stable stagehand-server-v3/vX.Y.Z release found in browserbase/stagehand'); | |
| return; | |
| } | |
| core.info(`Using stagehand/server-v3 release tag: ${best.release.tag_name}`); | |
| core.setOutput('tag', best.release.tag_name); | |
| core.setOutput('id', String(best.release.id)); | |
| - name: Download stagehand/server SEA binary (from GitHub Release assets) | |
| env: | |
| GH_TOKEN: ${{ secrets.STAGEHAND_SOURCE_TOKEN || github.token }} | |
| RELEASE_TAG: ${{ steps.stagehand-server-release.outputs.tag }} | |
| RELEASE_ID: ${{ steps.stagehand-server-release.outputs.id }} | |
| ASSET_NAME: ${{ matrix.binary_name }} | |
| OUTPUT_PATH: ${{ matrix.output_path }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Ensure we only ship the binary for this runner's OS/arch. | |
| python - <<'PY' | |
| from pathlib import Path | |
| sea_dir = Path("src/stagehand/_sea") | |
| sea_dir.mkdir(parents=True, exist_ok=True) | |
| for p in sea_dir.glob("stagehand-*"): | |
| p.unlink(missing_ok=True) | |
| for p in sea_dir.glob("*.exe"): | |
| p.unlink(missing_ok=True) | |
| PY | |
| echo "Downloading ${ASSET_NAME} from browserbase/stagehand@${RELEASE_TAG}" | |
| url="$( | |
| curl -fsSL \ | |
| -H "Authorization: Bearer ${GH_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/browserbase/stagehand/releases/${RELEASE_ID}" \ | |
| | python -c 'import json,sys; d=json.load(sys.stdin); a=next((x for x in d.get("assets",[]) if x.get("name")==sys.argv[1]), None); print(a.get("url","") if a else "")' \ | |
| "${ASSET_NAME}" | |
| )" | |
| if [ -z "${url}" ]; then | |
| echo "Release asset not found: ${ASSET_NAME} (tag=${RELEASE_TAG})" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "$(dirname "${OUTPUT_PATH}")" | |
| curl -fsSL \ | |
| -H "Authorization: Bearer ${GH_TOKEN}" \ | |
| -H "Accept: application/octet-stream" \ | |
| "${url}" \ | |
| -o "${OUTPUT_PATH}" | |
| chmod +x "${OUTPUT_PATH}" 2>/dev/null || true | |
| rm -f src/stagehand/_sea/.keep || true | |
| - name: Build wheel | |
| env: | |
| STAGEHAND_WHEEL_TAG: py3-none-${{ matrix.wheel_platform_tag }} | |
| run: uv build --wheel | |
| - name: Log SEA contents | |
| shell: bash | |
| run: | | |
| echo "Contents of src/stagehand/_sea/" | |
| ls -al src/stagehand/_sea || true | |
| python - <<'PY' | |
| import pathlib, zipfile, collections, sys | |
| had_error = False | |
| for wheel in sorted(pathlib.Path("dist").glob("*.whl")): | |
| print(f"Contents of {wheel.name} entries matching stagehand/_sea") | |
| with zipfile.ZipFile(wheel, "r") as zf: | |
| names = [info.filename for info in zf.infolist()] | |
| counts = collections.Counter(names) | |
| dups = sorted([name for name, n in counts.items() if n > 1]) | |
| for info in zf.infolist(): | |
| if "stagehand/_sea/" in info.filename: | |
| print(info.filename) | |
| if dups: | |
| had_error = True | |
| print("ERROR: duplicate zip entries detected:") | |
| for name in dups: | |
| print(f" {counts[name]}x {name}") | |
| if had_error: | |
| sys.exit(1) | |
| PY | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheel-${{ matrix.binary_name }} | |
| path: dist/*.whl | |
| retention-days: 7 | |
| build_sdist: | |
| name: build sdist | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| version: "0.9.13" | |
| - name: Build sdist | |
| run: uv build --sdist | |
| - name: Upload sdist artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| retention-days: 7 | |
| publish: | |
| name: publish | |
| needs: [build_wheels, build_sdist] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| version: "0.9.13" | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| - name: Flatten dist directory | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist_out | |
| find dist -type f \( -name "*.whl" -o -name "*.tar.gz" \) -print0 | while IFS= read -r -d '' f; do | |
| cp -f "$f" dist_out/ | |
| done | |
| ls -la dist_out | |
| - name: Publish to PyPI | |
| env: | |
| PYPI_TOKEN: ${{ secrets.STAGEHAND_PYPI_TOKEN || secrets.PYPI_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| uv publish --token="$PYPI_TOKEN" dist_out/* |