Files
browser-cli/scripts/package_extension.py
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

79 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Package the Chrome extension.
Default builds a local/unpacked-style archive that keeps manifest.key so the
extension ID stays stable for native messaging. ``--webstore`` writes the same
runtime files but strips ``key`` from manifest.json because the Chrome Web Store
rejects that field.
"""
from __future__ import annotations
import argparse
import json
import shutil
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EXTENSION_DIR = ROOT / "extension"
DIST_DIR = ROOT / "dist"
RUNTIME_FILES = (
"manifest.json",
"background.js",
"content-dispatch.js",
"content.js",
"icon.svg",
)
RUNTIME_DIRS = ("icons",)
def _read_manifest(webstore: bool) -> dict:
manifest = json.loads((EXTENSION_DIR / "manifest.json").read_text(encoding="utf-8"))
if webstore:
manifest.pop("key", None)
return manifest
def _copy_tree(src: Path, dst: Path) -> None:
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
def package_extension(*, webstore: bool = False, out: Path | None = None) -> Path:
manifest = _read_manifest(webstore)
version = manifest["version"]
suffix = "webstore" if webstore else "local"
out = out or DIST_DIR / f"browser-cli-extension-{suffix}-v{version}.zip"
staging = DIST_DIR / f"extension-package-{suffix}"
if staging.exists():
shutil.rmtree(staging)
staging.mkdir(parents=True)
out.parent.mkdir(parents=True, exist_ok=True)
for file_name in RUNTIME_FILES:
source = EXTENSION_DIR / file_name
if file_name == "manifest.json":
(staging / file_name).write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
else:
shutil.copy2(source, staging / file_name)
for dir_name in RUNTIME_DIRS:
_copy_tree(EXTENSION_DIR / dir_name, staging / dir_name)
if out.exists():
out.unlink()
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for path in sorted(staging.rglob("*")):
if path.is_file():
zf.write(path, path.relative_to(staging).as_posix())
return out
def main() -> None:
parser = argparse.ArgumentParser(description="Package browser-cli extension")
parser.add_argument("--webstore", action="store_true", help="strip manifest.key for Chrome Web Store upload")
parser.add_argument("--out", type=Path, default=None, help="output zip path")
args = parser.parse_args()
print(package_extension(webstore=args.webstore, out=args.out))
if __name__ == "__main__":
main()