Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 249 additions & 0 deletions .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
name: Publish browser4-cli

on:
push:
tags:
- 'cli-v*'
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 0.1.0)'
required: true
type: string
npm_tag:
description: 'NPM dist-tag'
required: false
default: 'latest'
type: choice
options:
- latest
- beta
- next
- rc

# Prevent two releases from running at the same time
concurrency:
group: publish-cli-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: write # needed to create a GitHub Release
id-token: write # needed for npm provenance

jobs:
# ─── 1. Build & test ──────────────────────────────────────────────────────────
build:
name: Build & Test
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: sdks/browser4-cli/package-lock.json

- name: Resolve version
id: ver
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
NPM_TAG="${{ github.event.inputs.npm_tag }}"
else
# Validate and strip leading 'cli-v' from tag: cli-v0.1.0 → 0.1.0
FULL_TAG="${GITHUB_REF#refs/tags/}"
if [[ ! "$FULL_TAG" =~ ^cli-v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
echo "❌ Unexpected tag format: '$FULL_TAG' — expected cli-v<X.Y.Z>"
exit 1
fi
VERSION="${FULL_TAG#cli-v}"
if [[ "$VERSION" == *"-beta"* ]]; then NPM_TAG="beta"
elif [[ "$VERSION" == *"-rc"* ]]; then NPM_TAG="rc"
elif [[ "$VERSION" == *"-next"* ]]; then NPM_TAG="next"
else NPM_TAG="latest"
fi
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT"
echo "📦 Version: $VERSION (dist-tag: $NPM_TAG)"

- name: Install dependencies
working-directory: sdks/browser4-cli
run: npm ci

- name: Lint
working-directory: sdks/browser4-cli
run: npm run lint

- name: Run unit tests
working-directory: sdks/browser4-cli
run: npm test

- name: Build
working-directory: sdks/browser4-cli
run: npm run build

- name: Verify build artefacts
working-directory: sdks/browser4-cli
run: npm run verify:build

- name: Stamp version into package.json
working-directory: sdks/browser4-cli
run: npm version ${{ steps.ver.outputs.version }} --no-git-tag-version

- name: Dry-run pack (inspect contents)
working-directory: sdks/browser4-cli
run: npm pack --dry-run

- name: Upload dist artefacts
uses: actions/upload-artifact@v4
with:
name: cli-dist
path: |
sdks/browser4-cli/dist/
sdks/browser4-cli/package.json
if-no-files-found: error

outputs:
version: ${{ steps.ver.outputs.version }}
npm_tag: ${{ steps.ver.outputs.npm_tag }}

# ─── 2. Cross-platform smoke-test ─────────────────────────────────────────────
smoke-test:
name: Smoke test (${{ matrix.os }}, Node ${{ matrix.node }})
needs: build
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ['18', '20']

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- name: Download dist artefacts
uses: actions/download-artifact@v4
with:
name: cli-dist
path: sdks/browser4-cli

- name: Install production dependencies
working-directory: sdks/browser4-cli
run: npm ci --omit=dev

- name: Verify CLI binary is executable
working-directory: sdks/browser4-cli
run: node dist/cli.js --version

# ─── 3. Publish to npm ────────────────────────────────────────────────────────
publish:
name: Publish to npm
needs: [build, smoke-test]
runs-on: ubuntu-latest
timeout-minutes: 10

environment:
name: npm
url: https://www.npmjs.com/package/@platonai/browser4-cli

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'

- name: Download dist artefacts
uses: actions/download-artifact@v4
with:
name: cli-dist
path: sdks/browser4-cli

- name: Install production dependencies
working-directory: sdks/browser4-cli
run: npm ci --omit=dev

- name: Publish
working-directory: sdks/browser4-cli
run: npm publish --access public --tag ${{ needs.build.outputs.npm_tag }} --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Post-publish verification
run: |
echo "⏳ Waiting for npm registry propagation…"
sleep 30
echo "✅ Published @platonai/browser4-cli@${{ needs.build.outputs.version }} (tag: ${{ needs.build.outputs.npm_tag }})"
echo "🔗 https://www.npmjs.com/package/@platonai/browser4-cli/v/${{ needs.build.outputs.version }}"

# ─── 4. GitHub Release ────────────────────────────────────────────────────────
github-release:
name: Create GitHub Release
needs: [build, publish]
runs-on: ubuntu-latest
timeout-minutes: 10
# Only create a GitHub Release when triggered by a tag push
if: github.event_name == 'push'

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref }}
name: browser4-cli ${{ needs.build.outputs.version }}
body: |
# @platonai/browser4-cli ${{ needs.build.outputs.version }}

## Installation

```bash
npm install -g @platonai/browser4-cli@${{ needs.build.outputs.version }}
# or
npx @platonai/browser4-cli@${{ needs.build.outputs.version }} --help
```

## Changes

See [CHANGELOG.md](https://github.com/platonai/Browser4/blob/main/sdks/browser4-cli/CHANGELOG.md) for details.

## Documentation

- [README](https://github.com/platonai/Browser4/tree/main/sdks/browser4-cli/README.md)
- [npm package](https://www.npmjs.com/package/@platonai/browser4-cli/v/${{ needs.build.outputs.version }})
draft: false
prerelease: ${{ contains(needs.build.outputs.version, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Release summary
run: |
{
echo "## 🚀 Release Summary"
echo ""
echo "| Field | Value |"
echo "| ------- | ----- |"
echo "| Version | \`${{ needs.build.outputs.version }}\` |"
echo "| npm tag | \`${{ needs.build.outputs.npm_tag }}\` |"
echo "| npm URL | https://www.npmjs.com/package/@platonai/browser4-cli/v/${{ needs.build.outputs.version }} |"
echo "| GitHub Release | https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }} |"
} >> "$GITHUB_STEP_SUMMARY"
34 changes: 34 additions & 0 deletions bin/release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,37 @@
- run release-tag-add.ps1 or release-tag-add.sh to add a new git tag
- wait for CI to build and publish to GitHub releases
- run next-minor.ps1 or next-minor.sh to bump version for next development cycle

# Release browser4-cli (npm)

1. Ensure all tests pass locally:
```bash
cd sdks/browser4-cli
npm ci && npm run build && npm test
```
2. Update `CHANGELOG.md` in `sdks/browser4-cli/` with the new version section.
3. Commit any outstanding changes and merge to master/main.
4. Run the release tagging script — it updates `package.json`, commits the bump,
creates an annotated tag, and pushes it:
```bash
# Linux / macOS
./bin/release/release-cli.sh 0.2.0

# Windows PowerShell
.\bin\release\release-cli.ps1 0.2.0
```
5. The pushed tag (`cli-v<version>`) triggers the **publish-cli** GitHub Actions
workflow, which:
- Lints, tests, and builds the CLI on ubuntu-latest
- Smoke-tests the built artefact on Ubuntu, macOS, and Windows (Node 18 & 20)
- Publishes `@platonai/browser4-cli@<version>` to the npm registry
- Creates a GitHub Release at
`https://github.com/platonai/Browser4/releases/tag/cli-v<version>`

For pre-releases (beta / RC), append a pre-release suffix:
```bash
./bin/release/release-cli.sh 0.2.0-beta.1
./bin/release/release-cli.sh 0.2.0-rc.1
```
Pre-release versions are published with the corresponding npm dist-tag
(`beta`, `rc`) instead of `latest`.
104 changes: 104 additions & 0 deletions bin/release/release-cli.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Tag a new browser4-cli release and push to remote.

.DESCRIPTION
1. Validates the version string.
2. Updates the version in sdks/browser4-cli/package.json.
3. Creates and pushes a git tag "cli-v<version>".
4. The tag push triggers the publish-cli.yml GitHub Actions workflow,
which builds, tests, publishes to npm, and creates a GitHub release.

.PARAMETER Version
Release version (e.g., 0.2.0 or 0.2.0-rc.1).

.EXAMPLE
.\bin\release\release-cli.ps1 0.2.0
.\bin\release\release-cli.ps1 0.2.0-rc.1
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Version
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Die([string]$Msg) { Write-Error "❌ $Msg"; exit 1 }

# ── Validate version ──────────────────────────────────────────────────────────

if ($Version -notmatch '^\d+\.\d+\.\d+(-[a-zA-Z0-9._-]+)?$') {
Die "Invalid version '$Version'. Expected format: X.Y.Z or X.Y.Z-<pre-release>"
}

$Tag = "cli-v$Version"

# ── Project root ──────────────────────────────────────────────────────────────

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$AppHome = git -C $ScriptDir rev-parse --show-toplevel
$CliDir = Join-Path $AppHome 'sdks/browser4-cli'

if (-not (Test-Path "$CliDir/package.json")) {
Die "sdks/browser4-cli/package.json not found."
}

Set-Location $AppHome

# ── Check working tree ────────────────────────────────────────────────────────

$status = git status --porcelain
if ($status) {
Die "Uncommitted changes detected. Commit or stash them before releasing."
}

# ── Duplicate tag guard ───────────────────────────────────────────────────────

$localTag = git rev-parse $Tag 2>$null
if ($LASTEXITCODE -eq 0) { Die "Tag '$Tag' already exists locally." }

$remoteTag = git ls-remote --tags origin $Tag
if ($remoteTag) { Die "Tag '$Tag' already exists on remote." }

# ── Bump version in package.json ─────────────────────────────────────────────

Write-Host "📝 Updating sdks/browser4-cli/package.json → $Version"
$pkgPath = "$CliDir/package.json"
$pkg = Get-Content $pkgPath -Raw | ConvertFrom-Json
$pkg.version = $Version
$pkg | ConvertTo-Json -Depth 10 | Set-Content $pkgPath

# Also update the version constant in src/version.ts
$VersionFile = "$CliDir/src/version.ts"
if (Test-Path $VersionFile) {
(Get-Content $VersionFile -Raw) -replace "export const VERSION = '.*';", "export const VERSION = '$Version';" |
Set-Content $VersionFile
Write-Host "📝 Updated src/version.ts → $Version"
}

# ── Commit version bump ───────────────────────────────────────────────────────

git add "$pkgPath" "$VersionFile" 2>$null
$staged = git diff --cached --name-only
if ($staged) {
git commit -m "chore(cli): bump version to $Version"
git push
Write-Host "✅ Version bump committed and pushed."
} else {
Write-Host "ℹ️ No version changes to commit (already at $Version)."
}

# ── Create and push tag ───────────────────────────────────────────────────────

Write-Host "🏷️ Creating tag $Tag …"
git tag -a $Tag -m "Release browser4-cli $Version"
git push origin $Tag

Write-Host ""
Write-Host "✅ Tag '$Tag' pushed. The publish-cli workflow will now:"
Write-Host " 1. Build and test the CLI"
Write-Host " 2. Publish @platonai/browser4-cli@$Version to npm"
Write-Host " 3. Create a GitHub Release at https://github.com/platonai/Browser4/releases/tag/$Tag"
Loading