fix: enterprise browser tab reuse, task API port, workflow cancellation #136
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
| 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 | |