Skip to content
Merged
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
87 changes: 87 additions & 0 deletions .github/scripts/test-release-version.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Dependency-free release gate regression tests; compatible with PowerShell 5.1.
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$validator = Join-Path $PSScriptRoot 'verify-release-version.ps1'
$fixture = Join-Path ([IO.Path]::GetTempPath()) ('browser-cli-version-' + [Guid]::NewGuid().ToString('N'))
$resolvedFixture = [IO.Path]::GetFullPath($fixture)
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
if (-not $resolvedFixture.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase)) { throw 'Fixture is outside temporary directory' }
New-Item -ItemType Directory -Path (Join-Path $fixture 'skills/lexmount-browser/scripts') -Force | Out-Null
$cases = [Collections.Generic.List[string]]::new()
$utf8 = [Text.UTF8Encoding]::new($false)

function Write-Fixture([string]$Path, [string]$Text) {
[IO.File]::WriteAllText((Join-Path $fixture $Path), $Text, $utf8)
}
function Reset-Fixture([string]$Version = '1.2.3') {
Write-Fixture 'Cargo.toml' "[package]`nname = `"lexmount-browser`"`nversion = `"$Version`"`n[dependencies]`nother = `"9.8.7`"`n"
Write-Fixture 'Cargo.lock' "version = 4`n[[package]]`nname = `"dependency`"`nversion = `"9.8.7`"`n[[package]]`nname = `"lexmount-browser`"`nversion = `"$Version`"`n"
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.ps1' ('$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "' + $Version + '" }' + "`n")
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.sh' ('version="${LEXMOUNT_BROWSER_CLI_VERSION:-' + $Version + '}"' + "`n")
}
function Pass([string]$Name, [scriptblock]$Action) {
& $Action
$cases.Add($Name)
Write-Host "PASS $Name"
}
function Reject([string]$Name, [scriptblock]$Action, [string]$Expected) {
$errorText = $null
try { & $Action | Out-Null } catch { $errorText = $_.Exception.Message }
if (-not $errorText -or $errorText -notlike "*$Expected*") { throw "$Name did not reject with '$Expected': $errorText" }
$cases.Add($Name)
Write-Host "PASS $Name"
}
try {
Reset-Fixture
Pass 'matching source and tag' {
$result = & $validator -RepositoryRoot $fixture -ReleaseTag 'v1.2.3'
if ($result.Version -cne '1.2.3' -or $result.VerifiedFiles -ne 4) { throw 'Unexpected validation result' }
}
Pass 'source-only PR check' { & $validator -RepositoryRoot $fixture | Out-Null }
foreach ($tag in @('v1.2.2', '1.2.3', 'V1.2.3', 'v1.2.3-extra', '')) {
Reject "wrong tag [$tag]" { & $validator -RepositoryRoot $fixture -ReleaseTag $tag } 'Release tag'
}
Reset-Fixture '1.2.1'
Reject 'published 1.2.2 incident: every source version still 1.2.1' { & $validator -RepositoryRoot $fixture -ReleaseTag 'v1.2.2' } 'Release tag'
Reset-Fixture
Write-Fixture 'Cargo.lock' "[[package]]`nname = `"lexmount-browser`"`nversion = `"1.2.1`"`n"
Reject 'stale lockfile' { & $validator -RepositoryRoot $fixture } 'Cargo.lock version'
Reset-Fixture
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.ps1' '$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.2.1" }'
Reject 'stale Windows bootstrap' { & $validator -RepositoryRoot $fixture } 'bootstrap.ps1 version'
Reset-Fixture
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.sh' 'version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.1}"'
Reject 'stale Mac bootstrap' { & $validator -RepositoryRoot $fixture } 'bootstrap.sh version'
Reset-Fixture
Write-Fixture 'Cargo.lock' "[[package]]`nname = `"some-dependency`"`nversion = `"1.2.3`"`n"
Reject 'dependency version cannot stand in for package' { & $validator -RepositoryRoot $fixture } 'exactly one lexmount-browser'
Reset-Fixture
$lock = [IO.File]::ReadAllText((Join-Path $fixture 'Cargo.lock'))
Write-Fixture 'Cargo.lock' ($lock + "[[package]]`nname = `"lexmount-browser`"`nversion = `"1.2.3`"`n")
Reject 'duplicate package' { & $validator -RepositoryRoot $fixture } 'exactly one lexmount-browser'
Reset-Fixture
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.sh' '# version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.3}"'
Reject 'comment is not a bootstrap version' { & $validator -RepositoryRoot $fixture } 'shell bootstrap default'
Reset-Fixture
$bootstrap = [IO.File]::ReadAllText((Join-Path $fixture 'skills/lexmount-browser/scripts/bootstrap.sh'))
Write-Fixture 'skills/lexmount-browser/scripts/bootstrap.sh' ($bootstrap + $bootstrap)
Reject 'duplicate bootstrap declarations' { & $validator -RepositoryRoot $fixture } 'shell bootstrap default'
Reset-Fixture '1.2.3-rc.1'
Pass 'matching prerelease' { & $validator -RepositoryRoot $fixture -ReleaseTag 'v1.2.3-rc.1' | Out-Null }
Reset-Fixture
foreach ($path in @('Cargo.toml', 'Cargo.lock', 'skills/lexmount-browser/scripts/bootstrap.ps1', 'skills/lexmount-browser/scripts/bootstrap.sh')) {
Write-Fixture $path ([IO.File]::ReadAllText((Join-Path $fixture $path)).Replace("`n", "`r`n"))
}
Pass 'Windows line endings' { & $validator -RepositoryRoot $fixture -ReleaseTag 'v1.2.3' | Out-Null }
Pass 'actual repository source' { & $validator | Out-Null }
Pass 'standalone -File entry point' {
$currentVersion = (& $validator).Version
$engine = (Get-Process -Id $PID).Path
& $engine -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $validator -ReleaseTag "v$currentVersion" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Standalone release gate exited $LASTEXITCODE" }
}
[pscustomobject]@{ Passed = $cases.Count; Cases = @($cases) } | ConvertTo-Json -Depth 3
} finally {
# Only this invocation's prevalidated GUID fixture, never the temp root.
Remove-Item -LiteralPath $resolvedFixture -Recurse -Force
}
49 changes: 49 additions & 0 deletions .github/scripts/verify-release-version.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Source-only release gate. Never runs bootstrap scripts or changes versions.
[CmdletBinding()]
param(
[string]$RepositoryRoot,
[AllowEmptyString()][string]$ReleaseTag
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
if (-not $PSBoundParameters.ContainsKey('RepositoryRoot')) {
$RepositoryRoot = Join-Path $PSScriptRoot '../..'
}

function Read-OneMatch([string]$Text, [string]$Pattern, [string]$Label) {
$found = [regex]::Matches($Text, $Pattern)
if ($found.Count -ne 1) { throw "Expected exactly one $Label; found $($found.Count)" }
return $found[0].Groups[1].Value
}

# These deliberately accept the repository's literal version declarations only.
# Fail closed if the manifest/bootstrap layout changes; never guess a version
# from a comment, dependency, environment override or a different package.
$manifest = Get-Content -LiteralPath (Join-Path $RepositoryRoot 'Cargo.toml') -Raw
$package = Read-OneMatch $manifest '(?ms)^\[package\][ \t]*\r?\n(.*?)(?=^\[|\z)' 'Cargo.toml [package] section'
$name = Read-OneMatch $package '(?m)^name[ \t]*=[ \t]*"([^"]+)"[ \t]*\r?$' 'package name'
if ($name -cne 'lexmount-browser') { throw "Unexpected package name: $name" }
$version = Read-OneMatch $package '(?m)^version[ \t]*=[ \t]*"([^"]+)"[ \t]*\r?$' 'package version'
$semver = '(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?'
if ($version -cnotmatch "^$semver$") { throw "Invalid literal package version: $version" }

$lock = Get-Content -LiteralPath (Join-Path $RepositoryRoot 'Cargo.lock') -Raw
$entries = @([regex]::Matches($lock, '(?ms)^\[\[package\]\][ \t]*\r?\n(.*?)(?=^\[|\z)') |
Where-Object { $_.Groups[1].Value -match '(?m)^name[ \t]*=[ \t]*"lexmount-browser"[ \t]*\r?$' })
if ($entries.Count -ne 1) { throw 'Expected exactly one lexmount-browser entry in Cargo.lock' }
$lockVersion = Read-OneMatch $entries[0].Groups[1].Value '(?m)^version[ \t]*=[ \t]*"([^"]+)"[ \t]*\r?$' 'Cargo.lock package version'
$psBootstrap = Get-Content -LiteralPath (Join-Path $RepositoryRoot 'skills/lexmount-browser/scripts/bootstrap.ps1') -Raw
$shBootstrap = Get-Content -LiteralPath (Join-Path $RepositoryRoot 'skills/lexmount-browser/scripts/bootstrap.sh') -Raw
$psVersion = Read-OneMatch $psBootstrap '(?m)^\$version = if \(\$env:LEXMOUNT_BROWSER_CLI_VERSION\) \{ \$env:LEXMOUNT_BROWSER_CLI_VERSION \} else \{ "([^"]+)" \}[ \t]*\r?$' 'PowerShell bootstrap default'
$shVersion = Read-OneMatch $shBootstrap '(?m)^version="\$\{LEXMOUNT_BROWSER_CLI_VERSION:-([^}]+)\}"[ \t]*\r?$' 'shell bootstrap default'
foreach ($entry in @(
@{ Label = 'Cargo.lock'; Value = $lockVersion },
@{ Label = 'bootstrap.ps1'; Value = $psVersion },
@{ Label = 'bootstrap.sh'; Value = $shVersion }
)) {
if ($entry.Value -cne $version) { throw "$($entry.Label) version $($entry.Value) does not match Cargo.toml $version" }
}
if ($PSBoundParameters.ContainsKey('ReleaseTag') -and $ReleaseTag -cne "v$version") {
throw "Release tag '$ReleaseTag' does not match package/bootstrap version v$version"
}
[pscustomobject]@{ Version = $version; ReleaseTag = $ReleaseTag; VerifiedFiles = 4 }
8 changes: 3 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ jobs:
! grep -q 'skills/lexmount-browser/bin/' .github/workflows/release.yml
- run: bash -n scripts/sign_and_notarize_macos.sh scripts/upload-release-to-cos.sh
- run: sh -n scripts/package-skill.sh skills/lexmount-browser/scripts/bootstrap.sh skills/lexmount-browser/scripts/doctor.sh
- name: Verify Skill bootstrap version
run: |
package_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n 1)"
grep -Fq "else { \"${package_version}\" }" skills/lexmount-browser/scripts/bootstrap.ps1
grep -Fq "LEXMOUNT_BROWSER_CLI_VERSION:-${package_version}" skills/lexmount-browser/scripts/bootstrap.sh
- name: Verify Skill platform selection and PATH isolation
run: |
test_dir="$(mktemp -d)"
Expand Down Expand Up @@ -81,6 +76,9 @@ jobs:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Test release version consistency gate
shell: powershell
run: .\.github\scripts\test-release-version.ps1
- name: Test published bootstrap version selection
shell: powershell
run: .\.github\scripts\test-select-bootstrap-version.ps1
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ permissions:
contents: write

jobs:
validate-version:
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- name: Validate tag, Cargo and Skill versions before releasing
shell: powershell
env:
RELEASE_TAG: ${{ github.ref_name }}
run: .\.github\scripts\verify-release-version.ps1 -ReleaseTag $env:RELEASE_TAG

build-macos:
needs: validate-version
runs-on: macos-14
environment: macos-release
steps:
Expand All @@ -18,6 +31,12 @@ jobs:
targets: aarch64-apple-darwin
- run: cargo test --locked
- run: cargo build --release --locked --target aarch64-apple-darwin
- name: Verify compiled version before signing
shell: bash
run: |
actual="$(target/aarch64-apple-darwin/release/browser-cli --version)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run the version check on a matching macOS architecture

In build-macos, the macos-14 hosted runner is Intel/x86_64, while this command attempts to execute the newly built aarch64-apple-darwin binary. Every matching tagged release will therefore fail here with an incompatible-architecture error before signing, packaging, and publishing; use an ARM runner or verify the embedded version without executing the cross-compiled binary.

Useful? React with 👍 / 👎.

expected="browser-cli ${GITHUB_REF_NAME#v}"
[ "$actual" = "$expected" ] || { echo "Version mismatch: expected $expected, got $actual" >&2; exit 1; }
- name: Sign and notarize
env:
MACOS_DEVELOPER_ID_APPLICATION_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_P12_BASE64 }}
Expand All @@ -38,6 +57,7 @@ jobs:
path: browser-cli-v*-aarch64-apple-darwin*

build-linux:
needs: validate-version
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
Expand All @@ -48,6 +68,12 @@ jobs:
- run: cargo test --locked
- name: Build static Linux binary
run: cargo build --release --locked --target x86_64-unknown-linux-musl
- name: Verify compiled version before packaging
shell: bash
run: |
actual="$(target/x86_64-unknown-linux-musl/release/browser-cli --version)"
expected="browser-cli ${GITHUB_REF_NAME#v}"
[ "$actual" = "$expected" ] || { echo "Version mismatch: expected $expected, got $actual" >&2; exit 1; }
- name: Package
run: |
version="${GITHUB_REF_NAME#v}"
Expand All @@ -66,6 +92,7 @@ jobs:
path: browser-cli-v*-x86_64-unknown-linux-musl*

build-windows:
needs: validate-version
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -74,6 +101,12 @@ jobs:
targets: x86_64-pc-windows-msvc
- run: cargo test --locked
- run: cargo build --release --locked --target x86_64-pc-windows-msvc
- name: Verify compiled version before packaging
shell: bash
run: |
actual="$(target/x86_64-pc-windows-msvc/release/browser-cli.exe --version)"
expected="browser-cli ${GITHUB_REF_NAME#v}"
[ "$actual" = "$expected" ] || { echo "Version mismatch: expected $expected, got $actual" >&2; exit 1; }
- name: Package
shell: bash
run: |
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lexmount-browser"
version = "1.2.1"
version = "1.2.3"
edition = "2024"
license = "MIT"
description = "Native Rust SDK and CLI for Lexmount cloud browsers"
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ automatic action retries. These changes require a new CLI release; published
Explicit page selection is introduced in version 1.2.0. Check that the installed
binary's `browser-cli action --help` lists `--target-id`; the published 1.1.15
binary does not have it. The package version and both bootstrap scripts target
1.2.1 together. Merging or building this source does not publish release assets:
bootstrap can install 1.2.1 only after its binaries and checksums are published
1.2.3 together. Merging or building this source does not publish release assets:
bootstrap can install 1.2.3 only after its binaries and checksums are published
to COS. Until then, use a source build for local verification.

Every `action` command accepts an optional `--target-id`. Obtain the page's CDP
Expand Down Expand Up @@ -163,6 +163,26 @@ dependency rather than substituting an older binary for a task needing the new
feature. Release tags must match the Cargo and bootstrap versions; never
overwrite an existing release with changed binaries.

### Release checklist

1. In a reviewed PR, update the package version in `Cargo.toml`, the
`lexmount-browser` entry in `Cargo.lock`, and the defaults in both
`skills/lexmount-browser/scripts/bootstrap.ps1` and `bootstrap.sh`.
2. Run `.github/scripts/test-release-version.ps1` with Windows PowerShell 5.1
or PowerShell 7, then `.github/scripts/verify-release-version.ps1 -ReleaseTag
v1.2.3` (substitute the intended version). Complete CI and merge the PR.
3. Create the matching tag **on that merged commit**. Typing a new tag or
release title in GitHub does not update any source version. The release
workflow rejects inconsistent versions before building, signing or uploading.
4. Wait for every build and the publish job. Each platform's compiled binary
must report the tag's version before packaging. Verify the downloaded asset's
checksum and `browser-cli version`; the Skill ZIP must pin the same version.

The published `v1.2.2` assets include the error-reporting fixes, but were built
with Cargo version `1.2.1` and Skill bootstrap defaults `1.2.1`. They are
misversioned; use the corrected `v1.2.3` release once published. Do not retag or
overwrite `v1.2.2`: consumers may already have its original files and checksums.

Agents resolve bundled scripts and binaries from the directory containing the
loaded `SKILL.md`: Codex uses the absolute source path supplied in the Skill
metadata, Claude Code uses `${CLAUDE_SKILL_DIR}`, and WorkBuddy/CodeBuddy uses
Expand Down
2 changes: 1 addition & 1 deletion skills/lexmount-browser/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Introduced in 1.2.0; requires a binary whose `browser-cli action --help` lists
Check the actual Skill-local binary, not just the version of these instructions.

For an authorized upgrade, rerun the matching Skill-local bootstrap script only
after its pinned 1.2.0 release assets are available, then verify `version` and
after its pinned release assets are available, then verify `version` and
`action --help`. A merged PR or a newer Skill file does not publish or replace
the binary. If the release is unavailable or the upgrade is not authorized,
report the dependency or capability limitation; do not send unsupported flags
Expand Down
2 changes: 1 addition & 1 deletion skills/lexmount-browser/scripts/bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function Invoke-Tls12Download {
}
}

$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.2.1" }
$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.2.3" }
$downloadBaseUrl = if ($env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL) { $env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL.TrimEnd('/') } else { "https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli" }
$architecture = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
if ($architecture -ne "AMD64") { throw "Only Windows x64 is supported" }
Expand Down
2 changes: 1 addition & 1 deletion skills/lexmount-browser/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -eu

version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.1}"
version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.3}"
download_base_url="${LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL:-https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli}"
repo="${download_base_url%/}/v${version}"
case "$(uname -s)-$(uname -m)" in
Expand Down
Loading