1616 release :
1717 name : Release
1818 runs-on : ubuntu-latest
19+ outputs :
20+ server_published : ${{ steps.server_meta.outputs.server_published }}
21+ server_version : ${{ steps.server_meta.outputs.server_version }}
22+ server_tag : ${{ steps.server_meta.outputs.server_tag }}
1923 steps :
2024 - name : Checkout Repo
2125 uses : actions/checkout@v4
@@ -49,24 +53,35 @@ jobs:
4953 env :
5054 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
5155
52- - name : Tag stagehand/server version for GitHub Releases
53- env :
54- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
56+ - name : Detect stagehand/server publish
57+ id : server_meta
5558 run : |
5659 set -euo pipefail
5760
5861 SERVER_VERSION="$(node -p "require('./packages/server/package.json').version")"
5962 TAG="stagehand-server/v${SERVER_VERSION}"
60- BEFORE_SHA="${{ github.event.before }}"
61-
62- if [ -n "${BEFORE_SHA}" ] && [ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then
63- BEFORE_VERSION="$(git show "${BEFORE_SHA}:packages/server/package.json" 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['version'])" 2>/dev/null || true)"
64- if [ -n "${BEFORE_VERSION}" ] && [ "${BEFORE_VERSION}" = "${SERVER_VERSION}" ]; then
65- echo "No @browserbasehq/stagehand-server version change (${SERVER_VERSION}); skipping tag."
66- exit 0
67- fi
63+
64+ SERVER_PUBLISHED="false"
65+ if [ "${{ steps.changesets.outputs.published }}" = "true" ]; then
66+ SERVER_PUBLISHED="$(node -e "const pkgs=JSON.parse(process.env.PUBLISHED_PACKAGES||'[]'); process.stdout.write(pkgs.some(p=>p.name==='@browserbasehq/stagehand-server')?'true':'false');")"
6867 fi
6968
69+ echo "server_version=${SERVER_VERSION}" >> "$GITHUB_OUTPUT"
70+ echo "server_tag=${TAG}" >> "$GITHUB_OUTPUT"
71+ echo "server_published=${SERVER_PUBLISHED}" >> "$GITHUB_OUTPUT"
72+ env :
73+ PUBLISHED_PACKAGES : ${{ steps.changesets.outputs.publishedPackages }}
74+
75+ - name : Tag stagehand/server version for GitHub Releases
76+ if : steps.server_meta.outputs.server_published == 'true'
77+ env :
78+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
79+ run : |
80+ set -euo pipefail
81+
82+ TAG="${{ steps.server_meta.outputs.server_tag }}"
83+ SERVER_VERSION="${{ steps.server_meta.outputs.server_version }}"
84+
7085 git config user.name "github-actions[bot]"
7186 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
7287
@@ -87,3 +102,235 @@ jobs:
87102 pnpm run release-canary
88103 env :
89104 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
105+
106+ server_build_binaries :
107+ name : Build stagehand/server binaries (${{ matrix.os }})
108+ needs : release
109+ if : needs.release.outputs.server_published == 'true'
110+ strategy :
111+ fail-fast : false
112+ matrix :
113+ include :
114+ - os : ubuntu-latest
115+ platform : linux
116+ arch : x64
117+ binary_name : stagehand-server-linux-x64
118+
119+ - os : ubuntu-latest
120+ platform : linux
121+ arch : arm64
122+ binary_name : stagehand-server-linux-arm64
123+
124+ - os : macos-latest
125+ platform : darwin
126+ arch : arm64
127+ binary_name : stagehand-server-darwin-arm64
128+
129+ - os : macos-13
130+ platform : darwin
131+ arch : x64
132+ binary_name : stagehand-server-darwin-x64
133+
134+ - os : windows-latest
135+ platform : win32
136+ arch : x64
137+ binary_name : stagehand-server-win32-x64.exe
138+
139+ - os : windows-latest
140+ platform : win32
141+ arch : arm64
142+ binary_name : stagehand-server-win32-arm64.exe
143+
144+ runs-on : ${{ matrix.os }}
145+ steps :
146+ - name : Checkout repository
147+ uses : actions/checkout@v4
148+ with :
149+ fetch-depth : 0
150+
151+ - name : Setup pnpm
152+ uses : pnpm/action-setup@v4
153+
154+ - name : Setup Node.js
155+ uses : actions/setup-node@v4
156+ with :
157+ node-version : 22.x
158+ cache : pnpm
159+
160+ - name : Install dependencies
161+ env :
162+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD : " 1"
163+ run : pnpm install --frozen-lockfile
164+
165+ - name : Build binary (Linux/macOS x64/host arch)
166+ if : matrix.platform != 'win32' && !(matrix.platform == 'linux' && matrix.arch == 'arm64')
167+ shell : bash
168+ run : |
169+ CI=true pnpm --filter @browserbasehq/stagehand-server build:binary
170+
171+ - name : Build binary (Linux arm64)
172+ if : matrix.platform == 'linux' && matrix.arch == 'arm64'
173+ shell : bash
174+ run : |
175+ set -euo pipefail
176+
177+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
178+ # since the SEA bundle depends on the built SDK output and the server's `build`
179+ # also runs OpenAPI generation (unnecessary for binary packaging here).
180+ pnpm --filter @browserbasehq/stagehand build
181+ cd packages/server
182+ mkdir -p dist/sea
183+
184+ pnpm exec esbuild src/server.ts \
185+ --bundle \
186+ --platform=node \
187+ --format=cjs \
188+ --outfile=dist/sea/bundle.cjs \
189+ --log-level=warning
190+
191+ node --experimental-sea-config sea-config.json
192+
193+ BLOB="dist/sea/sea-prep.blob"
194+ if [ ! -f "${BLOB}" ]; then
195+ echo "Missing ${BLOB}; SEA blob generation failed." >&2
196+ exit 1
197+ fi
198+
199+ NODE_VERSION="$(node -p 'process.version')"
200+ NODE_TARBALL="node-${NODE_VERSION}-linux-arm64.tar.xz"
201+ NODE_URL="https://nodejs.org/dist/${NODE_VERSION}/${NODE_TARBALL}"
202+
203+ curl -fsSL "${NODE_URL}" -o "${NODE_TARBALL}"
204+ tar -xJf "${NODE_TARBALL}"
205+
206+ NODE_EXE="node-${NODE_VERSION}-linux-arm64/bin/node"
207+ if [ ! -f "${NODE_EXE}" ]; then
208+ echo "Missing downloaded Node binary at ${NODE_EXE}" >&2
209+ exit 1
210+ fi
211+
212+ OUT="dist/sea/${{ matrix.binary_name }}"
213+ cp "${NODE_EXE}" "${OUT}"
214+ chmod +x "${OUT}"
215+
216+ pnpm exec postject "${OUT}" NODE_SEA_BLOB "${BLOB}" \
217+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
218+
219+ ls -lh "${OUT}"
220+
221+ - name : Build binary (Windows)
222+ if : matrix.platform == 'win32' && matrix.arch == 'x64'
223+ shell : powershell
224+ run : |
225+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
226+ # since the SEA bundle depends on the built SDK output and the server's `build`
227+ # also runs OpenAPI generation (unnecessary for binary packaging here).
228+ pnpm --filter @browserbasehq/stagehand build
229+ Set-Location packages/server
230+ node -e "require('fs').mkdirSync('dist/sea',{recursive:true})"
231+ pnpm exec esbuild src/server.ts --bundle --platform=node --format=cjs --outfile=dist/sea/bundle.cjs --log-level=warning
232+ node --experimental-sea-config sea-config.json
233+ $blob = "dist/sea/sea-prep.blob"
234+ if (!(Test-Path $blob)) { throw "Missing blob at $blob; SEA blob generation failed." }
235+ Copy-Item (Get-Command node).Source -Destination "dist/sea/${{ matrix.binary_name }}"
236+ pnpm exec postject "dist/sea/${{ matrix.binary_name }}" NODE_SEA_BLOB $blob `
237+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
238+
239+ - name : Build binary (Windows arm64)
240+ if : matrix.platform == 'win32' && matrix.arch == 'arm64'
241+ shell : powershell
242+ run : |
243+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
244+ # since the SEA bundle depends on the built SDK output and the server's `build`
245+ # also runs OpenAPI generation (unnecessary for binary packaging here).
246+ pnpm --filter @browserbasehq/stagehand build
247+ Set-Location packages/server
248+ node -e "require('fs').mkdirSync('dist/sea',{recursive:true})"
249+ pnpm exec esbuild src/server.ts --bundle --platform=node --format=cjs --outfile=dist/sea/bundle.cjs --log-level=warning
250+ node --experimental-sea-config sea-config.json
251+
252+ $blob = "dist/sea/sea-prep.blob"
253+ if (!(Test-Path $blob)) { throw "Missing blob at $blob; SEA blob generation failed." }
254+
255+ $nodeVersion = node -p "process.version"
256+ $zipName = "node-$nodeVersion-win-arm64.zip"
257+ $url = "https://nodejs.org/dist/$nodeVersion/$zipName"
258+
259+ $tmp = Join-Path $env:RUNNER_TEMP "node-arm64"
260+ Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
261+ New-Item -ItemType Directory -Force -Path $tmp | Out-Null
262+
263+ $zipPath = Join-Path $tmp $zipName
264+ Invoke-WebRequest -Uri $url -OutFile $zipPath
265+ Expand-Archive -Path $zipPath -DestinationPath $tmp
266+
267+ $nodeExe = Join-Path $tmp "node-$nodeVersion-win-arm64\\node.exe"
268+ if (!(Test-Path $nodeExe)) { throw "Missing downloaded Node binary at $nodeExe" }
269+
270+ Copy-Item $nodeExe -Destination "dist/sea/${{ matrix.binary_name }}"
271+ pnpm exec postject "dist/sea/${{ matrix.binary_name }}" NODE_SEA_BLOB $blob `
272+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
273+
274+ - name : Upload artifact
275+ uses : actions/upload-artifact@v4
276+ with :
277+ name : ${{ matrix.binary_name }}
278+ path : packages/server/dist/sea/${{ matrix.binary_name }}
279+ retention-days : 7
280+
281+ server_publish_release :
282+ name : Publish stagehand/server GitHub Release
283+ needs : [release, server_build_binaries]
284+ if : needs.release.outputs.server_published == 'true'
285+ runs-on : ubuntu-latest
286+ env :
287+ OAS_PATH : packages/server/openapi.v3.yaml
288+ steps :
289+ - name : Checkout repository
290+ uses : actions/checkout@v4
291+ with :
292+ fetch-depth : 0
293+
294+ - name : Prepare release assets directory
295+ run : mkdir -p release-assets
296+
297+ - name : Prepare stagehand/server release assets
298+ run : |
299+ set -euo pipefail
300+ cp "${{ env.OAS_PATH }}" "release-assets/openapi.v3.stagehand-server-${{ needs.release.outputs.server_version }}.yaml"
301+
302+ - name : Download SEA binary artifacts
303+ uses : actions/download-artifact@v4
304+ with :
305+ pattern : stagehand-server-*
306+ path : release-assets
307+ merge-multiple : true
308+
309+ - name : Create checksums
310+ shell : bash
311+ run : |
312+ set -euo pipefail
313+ cd release-assets
314+ # Only checksum binaries (exclude openapi yaml). Avoid failing if no matches.
315+ shopt -s nullglob
316+ files=(stagehand-server-*)
317+ bins=()
318+ for f in "${files[@]}"; do
319+ [[ "$f" == *openapi* ]] && continue
320+ [[ -f "$f" ]] && bins+=("$f")
321+ done
322+ : > checksums.sha256
323+ if [ "${#bins[@]}" -gt 0 ]; then
324+ shasum -a 256 "${bins[@]}" > checksums.sha256
325+ fi
326+
327+ - name : Publish stagehand/server GitHub release
328+ uses : softprops/action-gh-release@v2
329+ with :
330+ tag_name : ${{ needs.release.outputs.server_tag }}
331+ name : stagehand/server v${{ needs.release.outputs.server_version }}
332+ generate_release_notes : true
333+ files : |
334+ release-assets/openapi.v3.stagehand-server-${{ needs.release.outputs.server_version }}.yaml
335+ release-assets/stagehand-server-*
336+ release-assets/checksums.sha256
0 commit comments