Skip to content

fix: enterprise browser tab reuse, task API port, workflow cancellation #136

fix: enterprise browser tab reuse, task API port, workflow cancellation

fix: enterprise browser tab reuse, task API port, workflow cancellation #136

Workflow file for this run

name: deploy beta release
# Beta release pipeline: auto-version → bump → gate → publish → tag → GitHub Release.
#
# Flow (workflow_dispatch, manual button):
# 1. Derive next beta version from packages/cli/package.json base + highest
# existing vX.Y.Z-beta.N tag → produces X.Y.Z-beta.(N+1)
# 2. If packages/cli/package.json is not already at the target, bump it
# and record a chore commit (local only — push happens at the end)
# 3. npm ci + install Playwright
# 4. Run the prepublish gate (build, audit, gitleaks, manifest, install,
# e2e smoke). Escape hatches rejected under CI=true.
# 5. Publish to npm under the "beta" dist-tag using NPM_TOKEN
# 6. Tag vX.Y.Z-beta.N and push the bump commit + tag to main
# 7. Create a GitHub Release with auto-generated notes
#
# Recovery from a failed run:
# - Fails at gate / publish → nothing pushed anywhere, re-run to get N+1
# - Fails at git push → npm already has the tarball, push the tag manually
# and create the GitHub Release by hand
# - Fails at GH Release → tag exists, re-run is a no-op at bump/publish
# and will recreate the release
on:
push:
branches: [main]
permissions:
contents: write # push the bump commit + tag, create GH Release
packages: write # push Docker image to GHCR
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
# Skip the version-bump commit the workflow itself pushes back to main
if: "!contains(github.event.head_commit.message, '[skip ci]')"
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Compute next beta version
id: version
run: |
base=$(node -p "require('./packages/cli/package.json').version.replace(/-.*$/, '')")
# Highest N from git tags
tag_n=$(git tag -l "v${base}-beta.*" | sed "s/v${base}-beta\.//" | sort -n | tail -1)
# Current N from package.json (may be ahead of tags)
pkg_n=$(node -p "require('./packages/cli/package.json').version" | sed "s/.*-beta\.//" | grep -E '^[0-9]+$' || echo 0)
# Take the max of both, then add 1
max_n=$(( tag_n > pkg_n ? tag_n : pkg_n ))
next_n=$(( max_n + 1 ))
next="${base}-beta.${next_n}"
echo "tag_n=${tag_n:-0} pkg_n=${pkg_n} → next: ${next}"
echo "version=${next}" >> "$GITHUB_OUTPUT"
- name: Configure git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Bump CLI package version (if needed)
run: |
target='${{ steps.version.outputs.version }}'
current=$(node -p "require('./packages/cli/package.json').version")
if [ "$current" = "$target" ]; then
echo "packages/cli/package.json already at $target — skipping bump"
else
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('packages/cli/package.json', 'utf8'));
p.version = '$target';
fs.writeFileSync('packages/cli/package.json', JSON.stringify(p, null, 2) + '\n');
"
git add packages/cli/package.json
git commit -m "chore(release): v$target [skip ci]"
fi
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run prepublish gate
run: npm run gate -w granclaw
env:
CI: 'true'
# Step 6 (Playwright e2e) auto-skips when the claude CLI is unavailable
# in the runner. Steps 1-5 are hard blocks. Escape hatches
# (GRANCLAW_GATE_SKIP_*) are rejected under CI=true by the gate script.
- name: Publish to npm
run: npm publish --tag beta --access public --no-provenance -w granclaw
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Tag and push
run: |
git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}"
git push origin HEAD --follow-tags
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ steps.version.outputs.version }}"
generate_release_notes: true
- name: Summary
run: |
{
echo "### granclaw@${{ steps.version.outputs.version }} released"
echo ""
echo "- npm: https://www.npmjs.com/package/granclaw/v/${{ steps.version.outputs.version }}"
echo "- tag: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}"
} >> "$GITHUB_STEP_SUMMARY"
build-docker:
name: Build & push Docker image
needs: release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./infra
push: true
# Emit both `:latest` and `:<version>` so downstream images
# (enterprise/infra/Dockerfile.granclaw) can pin to an exact base
# version. Without the versioned tag, enterprise is forced to
# track :latest and every base release silently rebuilds it.
tags: |
ghcr.io/aitrace-dev/granclaw:latest
ghcr.io/aitrace-dev/granclaw:${{ needs.release.outputs.version }}
build-args: GRANCLAW_VERSION=${{ needs.release.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max