Files
browser-cli/.gitea/workflows/package-extension.yml
T
daniel156161 5cec57e06d
Testing / remote-protocol-compat (0.9.3) (push) Successful in 40s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 38s
Testing / test (push) Failing after 1m3s
Package Extension / package-extension (push) Successful in 29s
Build & Publish Package / publish (push) Successful in 33s
feat!: harden raw browser control and packaging
- Add safe-by-default policy gates for raw command surfaces: command, script, and serve-http /command.

- Require explicit opt-ins for page reads, browser control, and high-risk commands such as dom.eval, storage.*, and screenshots.

- Remove all cookies support from CLI, SDK, extension commands, permissions, constants, docs, and tests.

- Add diagnostic, events, watch, workspace, remote, raw command, script, HTTP gateway, tree-view, session import/export, and extension info/capability commands.

- Add Chrome Web Store packaging that strips manifest.key while keeping local packages with a stable native-messaging extension ID.

- Bump browser-cli and extension version to 0.14.1 and cover the new behavior with pytest and extension packaging tests.

BREAKING CHANGE: cookies commands and the b.cookies SDK namespace have been removed; generic raw command execution now blocks non-safe commands unless explicitly allowed.
2026-06-14 14:33:15 +02:00

177 lines
5.6 KiB
YAML

name: Package Extension
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
jobs:
package-extension:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install extension build dependencies
run: npm ci
- name: Build extension
run: npm run check:extension
- name: Read extension version
id: version
run: |
version="$(python - <<'PY'
import json
from pathlib import Path
manifest = json.loads(Path("extension/manifest.json").read_text())
print(manifest["version"])
PY
)"
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Build extension archives
run: |
python scripts/package_extension.py --out "dist/browser-cli-extension-v${{ steps.version.outputs.version }}.zip"
python scripts/package_extension.py --webstore --out "dist/browser-cli-extension-webstore-v${{ steps.version.outputs.version }}.zip"
- name: Publish extension release asset
env:
ACTION_ACCESS_TOKEN: ${{ secrets.ACTION_ACCESS_TOKEN }}
ASSET_NAME: browser-cli-extension-v${{ steps.version.outputs.version }}.zip
EXTENSION_VERSION: ${{ steps.version.outputs.version }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_SHA: ${{ github.sha }}
run: |
set -euo pipefail
asset_path="dist/browser-cli-extension-v${EXTENSION_VERSION}.zip"
asset_name="$(basename "$asset_path")"
tag_name="v${EXTENSION_VERSION}"
api_base="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
if [ ! -f "$asset_path" ]; then
echo "Missing asset: $asset_path" >&2
exit 1
fi
release_body="$(mktemp)"
create_body="$(mktemp)"
trap 'rm -f "$release_body" "$create_body"' EXIT
release_status="$(curl --silent --show-error \
--output "$release_body" \
--write-out "%{http_code}" \
--header "Authorization: token ${ACTION_ACCESS_TOKEN}" \
--header "Accept: application/json" \
"${api_base}/releases/tags/${tag_name}")"
release_id="$(python - "$release_body" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as fh:
data = json.load(fh)
except (FileNotFoundError, json.JSONDecodeError):
print("")
raise SystemExit(0)
print(data.get("id", ""))
PY
)"
if [ "$release_status" = "404" ] || [ -z "$release_id" ]; then
create_payload="$(python - <<'PY'
import json
import os
version = os.environ["EXTENSION_VERSION"]
sha = os.environ["GITHUB_SHA"]
print(json.dumps({
"tag_name": f"v{version}",
"target_commitish": sha,
"name": f"v{version}",
"draft": False,
"prerelease": False,
}))
PY
)"
create_status="$(curl --silent --show-error \
--request POST \
--output "$create_body" \
--write-out "%{http_code}" \
--header "Authorization: token ${ACTION_ACCESS_TOKEN}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "$create_payload" \
"${api_base}/releases")"
if [ "$create_status" -lt 200 ] || [ "$create_status" -ge 300 ]; then
echo "Failed to create release for ${tag_name} (HTTP ${create_status})" >&2
cat "$create_body" >&2
exit 1
fi
release_id="$(python - "$create_body" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
print(data["id"])
PY
)"
cp "$create_body" "$release_body"
elif [ "$release_status" -lt 200 ] || [ "$release_status" -ge 300 ]; then
echo "Failed to fetch release for ${tag_name} (HTTP ${release_status})" >&2
cat "$release_body" >&2
exit 1
fi
existing_asset_id="$(python - "$release_body" <<'PY'
import json
import os
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
asset_name = os.environ["ASSET_NAME"]
for asset in data.get("assets", []):
if asset.get("name") == asset_name:
print(asset["id"])
break
else:
print("")
PY
)"
if [ -n "$existing_asset_id" ]; then
curl --silent --show-error \
--request DELETE \
--header "Authorization: token ${ACTION_ACCESS_TOKEN}" \
--header "Accept: application/json" \
"${api_base}/releases/${release_id}/assets/${existing_asset_id}"
fi
curl --silent --show-error \
--request POST \
--header "Authorization: token ${ACTION_ACCESS_TOKEN}" \
--header "Accept: application/json" \
--form "attachment=@${asset_path}" \
"${api_base}/releases/${release_id}/assets?name=${asset_name}"