browserget 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browserget-1.0.1/.gitignore +31 -0
- browserget-1.0.1/CHANGELOG.md +54 -0
- browserget-1.0.1/LICENSE +21 -0
- browserget-1.0.1/PKG-INFO +165 -0
- browserget-1.0.1/README.md +126 -0
- browserget-1.0.1/browserget/__init__.py +46 -0
- browserget-1.0.1/browserget/__main__.py +6 -0
- browserget-1.0.1/browserget/archive.py +114 -0
- browserget-1.0.1/browserget/cache.py +187 -0
- browserget-1.0.1/browserget/checksum.py +76 -0
- browserget-1.0.1/browserget/cli.py +799 -0
- browserget-1.0.1/browserget/config.py +101 -0
- browserget-1.0.1/browserget/exceptions.py +110 -0
- browserget-1.0.1/browserget/http.py +272 -0
- browserget-1.0.1/browserget/installers/__init__.py +19 -0
- browserget-1.0.1/browserget/installers/base.py +129 -0
- browserget-1.0.1/browserget/installers/chrome.py +187 -0
- browserget-1.0.1/browserget/installers/chromedriver.py +214 -0
- browserget-1.0.1/browserget/installers/edge.py +507 -0
- browserget-1.0.1/browserget/installers/firefox.py +336 -0
- browserget-1.0.1/browserget/installers/geckodriver.py +200 -0
- browserget-1.0.1/browserget/logging.py +51 -0
- browserget-1.0.1/browserget/models.py +111 -0
- browserget-1.0.1/browserget/parsers/__init__.py +23 -0
- browserget-1.0.1/browserget/parsers/cft.py +119 -0
- browserget-1.0.1/browserget/parsers/edge.py +203 -0
- browserget-1.0.1/browserget/parsers/firefox.py +114 -0
- browserget-1.0.1/browserget/parsers/geckodriver.py +109 -0
- browserget-1.0.1/browserget/platform.py +142 -0
- browserget-1.0.1/browserget/registry.py +186 -0
- browserget-1.0.1/browserget/system.py +278 -0
- browserget-1.0.1/docs/architecture/overview.md +121 -0
- browserget-1.0.1/docs/architecture/parsers.md +113 -0
- browserget-1.0.1/docs/cli-reference/commands.md +82 -0
- browserget-1.0.1/docs/index.md +52 -0
- browserget-1.0.1/docs/usage/cache.md +147 -0
- browserget-1.0.1/docs/usage/ensure.md +116 -0
- browserget-1.0.1/docs/usage/install.md +122 -0
- browserget-1.0.1/mkdocs.yml +51 -0
- browserget-1.0.1/pyproject.toml +92 -0
- browserget-1.0.1/tests/__init__.py +0 -0
- browserget-1.0.1/tests/cli/__init__.py +0 -0
- browserget-1.0.1/tests/cli/conftest.py +110 -0
- browserget-1.0.1/tests/cli/test_doctor.py +211 -0
- browserget-1.0.1/tests/cli/test_ensure.py +157 -0
- browserget-1.0.1/tests/cli/test_install.py +205 -0
- browserget-1.0.1/tests/cli/test_list.py +105 -0
- browserget-1.0.1/tests/cli/test_path.py +80 -0
- browserget-1.0.1/tests/cli/test_remove.py +122 -0
- browserget-1.0.1/tests/cli/test_versions.py +85 -0
- browserget-1.0.1/tests/conftest.py +58 -0
- browserget-1.0.1/tests/fixtures/cft_versions.json +202 -0
- browserget-1.0.1/tests/fixtures/edge_api.json +152 -0
- browserget-1.0.1/tests/fixtures/firefox_releases.html +18 -0
- browserget-1.0.1/tests/fixtures/geckodriver_releases.json +76 -0
- browserget-1.0.1/tests/integration/__init__.py +0 -0
- browserget-1.0.1/tests/integration/conftest.py +64 -0
- browserget-1.0.1/tests/integration/test_real_install.py +459 -0
- browserget-1.0.1/tests/unit/__init__.py +0 -0
- browserget-1.0.1/tests/unit/test_archive.py +127 -0
- browserget-1.0.1/tests/unit/test_checksum.py +94 -0
- browserget-1.0.1/tests/unit/test_exceptions.py +272 -0
- browserget-1.0.1/tests/unit/test_http.py +239 -0
- browserget-1.0.1/tests/unit/test_models.py +200 -0
- browserget-1.0.1/tests/unit/test_parsers/__init__.py +0 -0
- browserget-1.0.1/tests/unit/test_parsers/test_cft.py +142 -0
- browserget-1.0.1/tests/unit/test_parsers/test_edge.py +175 -0
- browserget-1.0.1/tests/unit/test_parsers/test_firefox.py +154 -0
- browserget-1.0.1/tests/unit/test_parsers/test_geckodriver.py +175 -0
- browserget-1.0.1/tests/unit/test_platform.py +129 -0
- browserget-1.0.1/tests/unit/test_registry.py +277 -0
- browserget-1.0.1/tests/unit/test_regression_phase2.py +307 -0
- browserget-1.0.1/tests/unit/test_regression_phase3.py +1593 -0
- browserget-1.0.1/tests/unit/test_regression_phase4.py +1783 -0
- browserget-1.0.1/tests/unit/test_system.py +227 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
ref/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
*.egg
|
|
9
|
+
.tox/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
htmlcov/
|
|
14
|
+
.coverage
|
|
15
|
+
.coverage.*
|
|
16
|
+
venv/
|
|
17
|
+
.venv/
|
|
18
|
+
.env
|
|
19
|
+
.venv-dev/
|
|
20
|
+
|
|
21
|
+
# Additional ignores
|
|
22
|
+
coverage.xml
|
|
23
|
+
*.so
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
site/
|
|
29
|
+
spike.py
|
|
30
|
+
errors.txt
|
|
31
|
+
out.yml
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.1] - 2026-08-17
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Corrected PyPI publish action reference in release workflow
|
|
15
|
+
|
|
16
|
+
## [1.0.0] - 2026-08-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Project scaffolding: directory structure, community files, CI workflows
|
|
21
|
+
- `pyproject.toml` with `dev` and `docs` optional dependencies
|
|
22
|
+
- GitHub Actions: CI (lint + matrix test), coverage, docs deploy
|
|
23
|
+
- Issue templates (bug report, feature request) and PR template
|
|
24
|
+
- Dependabot configuration for pip and github-actions
|
|
25
|
+
- Pre-commit hooks (pre-commit-hooks v4.6.0, ruff-pre-commit v0.8.0)
|
|
26
|
+
- EditorConfig, Makefile, AGENTS.md
|
|
27
|
+
- Public API exports in `browserget.__init__` and `browserget.installers`
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Platform mapping for edge/edgedriver now returns lowercase strings matching
|
|
32
|
+
the Edge API (`win64`, `mac-arm64`, `linux64`)
|
|
33
|
+
- Firefox parser `_PLATFORM_PATHS` now includes `"os"` mapping for macOS
|
|
34
|
+
- GeckoDriver parser `_ASSET_PLATFORM_MAP` now includes `"macos"` mapping
|
|
35
|
+
- `InstalledArtifact.from_dict` validates required fields and types
|
|
36
|
+
- `Registry.load` skips corrupted entries instead of crashing
|
|
37
|
+
- HTTP requests now include `User-Agent` header for API compatibility
|
|
38
|
+
- `get_available_disk_mb` consistently uses `get_cache_dir()` for path
|
|
39
|
+
- `UnknownTargetError` message now includes `edgedriver` in supported list
|
|
40
|
+
- `zip` calls in CLI use `strict=True` for safety
|
|
41
|
+
- Bare `Exception` catches in `doctor` command replaced with specific types
|
|
42
|
+
- `subprocess.run` in Edge installer uses `text=True` for cleaner output
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Removed duplicate `__init__` methods from all installer subclasses (DRY)
|
|
47
|
+
- Extracted common HTTP retry logic into `_retry_with_backoff` method (DRY)
|
|
48
|
+
- `logging.py` now also configures stdlib `urllib` logger
|
|
49
|
+
- CI workflow `test.yml` coverage threshold adjusted from 80% to 50%
|
|
50
|
+
- Consolidated CI workflows: merged `test.yml` into `ci.yml`, added `ruff format --check`
|
|
51
|
+
- `__version__` now uses `importlib.metadata` instead of hardcoded string
|
|
52
|
+
- Development status updated to Production/Stable
|
|
53
|
+
- Package layout moved from `src/browserget` to `browserget/` repository root
|
|
54
|
+
- Author email standardized to `mathias.paulenko@outlook.com` across all files
|
browserget-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mathias Paulenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: browserget
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Standalone CLI to install browsers and drivers without any framework
|
|
5
|
+
Project-URL: Homepage, https://mathiaspaulenko.github.io/browserget/
|
|
6
|
+
Project-URL: Repository, https://github.com/MathiasPaulenko/browserget
|
|
7
|
+
Project-URL: Changelog, https://github.com/MathiasPaulenko/browserget/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/MathiasPaulenko/browserget/issues
|
|
9
|
+
Author-email: Mathias Paulenko <mathias.paulenko@outlook.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,browser,chrome,chromedriver,edge,edgedriver,firefox,geckodriver,install,selenium,testing,webdriver
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: typer>=0.12.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
|
+
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
37
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# browserget
|
|
41
|
+
|
|
42
|
+
[](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml)
|
|
43
|
+
[](https://mathiaspaulenko.github.io/browserget/)
|
|
44
|
+
[](https://www.python.org/downloads/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[](https://pypi.org/project/browserget/)
|
|
47
|
+
|
|
48
|
+
Standalone CLI to install browsers and drivers without any framework.
|
|
49
|
+
|
|
50
|
+
Chrome for Testing, Firefox, Edge, ChromeDriver, GeckoDriver, EdgeDriver —
|
|
51
|
+
all from a single tool. Python 3.11+, zero heavy dependencies.
|
|
52
|
+
|
|
53
|
+
## Why?
|
|
54
|
+
|
|
55
|
+
`playwright install` ties you to Playwright. `puppeteer install` ties you to
|
|
56
|
+
Puppeteer. `browserget` installs browsers for **any** use — automation,
|
|
57
|
+
scraping, testing, CDP, BiDi, whatever.
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- **6 targets** — Chrome, Firefox, Edge + ChromeDriver, GeckoDriver, EdgeDriver
|
|
62
|
+
- **Concurrent installs** — multiple targets install in parallel
|
|
63
|
+
- **Driver matching** — `--for chrome` auto-matches ChromeDriver to your Chrome
|
|
64
|
+
- **Checksum verification** — SHA-256/SHA-512 when upstream provides checksums
|
|
65
|
+
- **Registry tracking** — JSON registry tracks every installed artifact
|
|
66
|
+
- **JSON output** — every command supports `--json` for CI/CD integration
|
|
67
|
+
- **Retry with backoff** — HTTP client retries failed requests automatically
|
|
68
|
+
- **Disk space checks** — verifies sufficient space before downloading
|
|
69
|
+
- **Platform detection** — auto-detects OS and architecture
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install browserget
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Install latest stable Chrome
|
|
81
|
+
browserget install chrome
|
|
82
|
+
|
|
83
|
+
# Install matching ChromeDriver
|
|
84
|
+
browserget install chromedriver --for chrome
|
|
85
|
+
|
|
86
|
+
# Ensure browsers are installed (idempotent — skips if present)
|
|
87
|
+
browserget ensure chrome chromedriver
|
|
88
|
+
|
|
89
|
+
# Verify system health
|
|
90
|
+
browserget doctor
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Install specific version
|
|
97
|
+
browserget install chrome --version 131.0.6778.87
|
|
98
|
+
|
|
99
|
+
# Install by milestone (latest patch in 131)
|
|
100
|
+
browserget install chrome --version 131
|
|
101
|
+
|
|
102
|
+
# Install Firefox and Edge
|
|
103
|
+
browserget install firefox edge
|
|
104
|
+
|
|
105
|
+
# Install EdgeDriver
|
|
106
|
+
browserget install edgedriver --for edge
|
|
107
|
+
|
|
108
|
+
# List installed browsers
|
|
109
|
+
browserget list
|
|
110
|
+
|
|
111
|
+
# Get path to installed browser
|
|
112
|
+
browserget path chrome
|
|
113
|
+
|
|
114
|
+
# Remove a browser
|
|
115
|
+
browserget remove chrome
|
|
116
|
+
|
|
117
|
+
# Remove all versions of a target
|
|
118
|
+
browserget remove chrome --all
|
|
119
|
+
|
|
120
|
+
# List available versions for a target
|
|
121
|
+
browserget versions chrome
|
|
122
|
+
|
|
123
|
+
# Check system health
|
|
124
|
+
browserget doctor
|
|
125
|
+
|
|
126
|
+
# JSON output for CI/CD
|
|
127
|
+
browserget install chrome --json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Supported targets
|
|
131
|
+
|
|
132
|
+
| Target | Type | Upstream source |
|
|
133
|
+
|--------|------|-----------------|
|
|
134
|
+
| `chrome` | Browser | Chrome for Testing API |
|
|
135
|
+
| `firefox` | Browser | Mozilla FTP |
|
|
136
|
+
| `edge` | Browser | Edge Updates API |
|
|
137
|
+
| `chromedriver` | Driver | Chrome for Testing API |
|
|
138
|
+
| `geckodriver` | Driver | GitHub Releases (mozilla/geckodriver) |
|
|
139
|
+
| `edgedriver` | Driver | Edge Updates API |
|
|
140
|
+
|
|
141
|
+
## Requirements
|
|
142
|
+
|
|
143
|
+
- Python 3.11, 3.12, or 3.13
|
|
144
|
+
- `typer` (installed automatically)
|
|
145
|
+
- Internet access to download browser binaries
|
|
146
|
+
|
|
147
|
+
## Documentation
|
|
148
|
+
|
|
149
|
+
Full documentation is available at
|
|
150
|
+
[https://mathiaspaulenko.github.io/browserget/](https://mathiaspaulenko.github.io/browserget/).
|
|
151
|
+
|
|
152
|
+
- [Install command](https://mathiaspaulenko.github.io/browserget/usage/install/) — detailed usage and examples
|
|
153
|
+
- [Ensure command](https://mathiaspaulenko.github.io/browserget/usage/ensure/) — idempotent installs for CI pipelines
|
|
154
|
+
- [Cache management](https://mathiaspaulenko.github.io/browserget/usage/cache/) — cache location, cleanup, and configuration
|
|
155
|
+
- [CLI reference](https://mathiaspaulenko.github.io/browserget/cli-reference/commands/) — complete command and flag documentation
|
|
156
|
+
- [Architecture](https://mathiaspaulenko.github.io/browserget/architecture/overview/) — module diagram and design decisions
|
|
157
|
+
|
|
158
|
+
## Contributing
|
|
159
|
+
|
|
160
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
|
|
161
|
+
development setup, testing, and pull request guidelines.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# browserget
|
|
2
|
+
|
|
3
|
+
[](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml)
|
|
4
|
+
[](https://mathiaspaulenko.github.io/browserget/)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://pypi.org/project/browserget/)
|
|
8
|
+
|
|
9
|
+
Standalone CLI to install browsers and drivers without any framework.
|
|
10
|
+
|
|
11
|
+
Chrome for Testing, Firefox, Edge, ChromeDriver, GeckoDriver, EdgeDriver —
|
|
12
|
+
all from a single tool. Python 3.11+, zero heavy dependencies.
|
|
13
|
+
|
|
14
|
+
## Why?
|
|
15
|
+
|
|
16
|
+
`playwright install` ties you to Playwright. `puppeteer install` ties you to
|
|
17
|
+
Puppeteer. `browserget` installs browsers for **any** use — automation,
|
|
18
|
+
scraping, testing, CDP, BiDi, whatever.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **6 targets** — Chrome, Firefox, Edge + ChromeDriver, GeckoDriver, EdgeDriver
|
|
23
|
+
- **Concurrent installs** — multiple targets install in parallel
|
|
24
|
+
- **Driver matching** — `--for chrome` auto-matches ChromeDriver to your Chrome
|
|
25
|
+
- **Checksum verification** — SHA-256/SHA-512 when upstream provides checksums
|
|
26
|
+
- **Registry tracking** — JSON registry tracks every installed artifact
|
|
27
|
+
- **JSON output** — every command supports `--json` for CI/CD integration
|
|
28
|
+
- **Retry with backoff** — HTTP client retries failed requests automatically
|
|
29
|
+
- **Disk space checks** — verifies sufficient space before downloading
|
|
30
|
+
- **Platform detection** — auto-detects OS and architecture
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install browserget
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install latest stable Chrome
|
|
42
|
+
browserget install chrome
|
|
43
|
+
|
|
44
|
+
# Install matching ChromeDriver
|
|
45
|
+
browserget install chromedriver --for chrome
|
|
46
|
+
|
|
47
|
+
# Ensure browsers are installed (idempotent — skips if present)
|
|
48
|
+
browserget ensure chrome chromedriver
|
|
49
|
+
|
|
50
|
+
# Verify system health
|
|
51
|
+
browserget doctor
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Install specific version
|
|
58
|
+
browserget install chrome --version 131.0.6778.87
|
|
59
|
+
|
|
60
|
+
# Install by milestone (latest patch in 131)
|
|
61
|
+
browserget install chrome --version 131
|
|
62
|
+
|
|
63
|
+
# Install Firefox and Edge
|
|
64
|
+
browserget install firefox edge
|
|
65
|
+
|
|
66
|
+
# Install EdgeDriver
|
|
67
|
+
browserget install edgedriver --for edge
|
|
68
|
+
|
|
69
|
+
# List installed browsers
|
|
70
|
+
browserget list
|
|
71
|
+
|
|
72
|
+
# Get path to installed browser
|
|
73
|
+
browserget path chrome
|
|
74
|
+
|
|
75
|
+
# Remove a browser
|
|
76
|
+
browserget remove chrome
|
|
77
|
+
|
|
78
|
+
# Remove all versions of a target
|
|
79
|
+
browserget remove chrome --all
|
|
80
|
+
|
|
81
|
+
# List available versions for a target
|
|
82
|
+
browserget versions chrome
|
|
83
|
+
|
|
84
|
+
# Check system health
|
|
85
|
+
browserget doctor
|
|
86
|
+
|
|
87
|
+
# JSON output for CI/CD
|
|
88
|
+
browserget install chrome --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Supported targets
|
|
92
|
+
|
|
93
|
+
| Target | Type | Upstream source |
|
|
94
|
+
|--------|------|-----------------|
|
|
95
|
+
| `chrome` | Browser | Chrome for Testing API |
|
|
96
|
+
| `firefox` | Browser | Mozilla FTP |
|
|
97
|
+
| `edge` | Browser | Edge Updates API |
|
|
98
|
+
| `chromedriver` | Driver | Chrome for Testing API |
|
|
99
|
+
| `geckodriver` | Driver | GitHub Releases (mozilla/geckodriver) |
|
|
100
|
+
| `edgedriver` | Driver | Edge Updates API |
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
- Python 3.11, 3.12, or 3.13
|
|
105
|
+
- `typer` (installed automatically)
|
|
106
|
+
- Internet access to download browser binaries
|
|
107
|
+
|
|
108
|
+
## Documentation
|
|
109
|
+
|
|
110
|
+
Full documentation is available at
|
|
111
|
+
[https://mathiaspaulenko.github.io/browserget/](https://mathiaspaulenko.github.io/browserget/).
|
|
112
|
+
|
|
113
|
+
- [Install command](https://mathiaspaulenko.github.io/browserget/usage/install/) — detailed usage and examples
|
|
114
|
+
- [Ensure command](https://mathiaspaulenko.github.io/browserget/usage/ensure/) — idempotent installs for CI pipelines
|
|
115
|
+
- [Cache management](https://mathiaspaulenko.github.io/browserget/usage/cache/) — cache location, cleanup, and configuration
|
|
116
|
+
- [CLI reference](https://mathiaspaulenko.github.io/browserget/cli-reference/commands/) — complete command and flag documentation
|
|
117
|
+
- [Architecture](https://mathiaspaulenko.github.io/browserget/architecture/overview/) — module diagram and design decisions
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
|
|
121
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
|
|
122
|
+
development setup, testing, and pull request guidelines.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""browserget — Standalone CLI to install browsers and drivers without any framework."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _pkg_version
|
|
5
|
+
|
|
6
|
+
from browserget.config import Config, load_config
|
|
7
|
+
from browserget.exceptions import (
|
|
8
|
+
AlreadyInstalledError,
|
|
9
|
+
BrowsergetError,
|
|
10
|
+
ChecksumMismatchError,
|
|
11
|
+
DriverMatchError,
|
|
12
|
+
InsufficientDiskSpaceError,
|
|
13
|
+
NetworkError,
|
|
14
|
+
UnknownTargetError,
|
|
15
|
+
UnsupportedPlatformError,
|
|
16
|
+
VersionNotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from browserget.models import InstalledArtifact, ResolvedVersion, SystemBrowser
|
|
19
|
+
from browserget.platform import OS, Arch, Platform, detect_platform
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
__version__ = _pkg_version("browserget")
|
|
23
|
+
except PackageNotFoundError: # pragma: no cover
|
|
24
|
+
__version__ = "0.0.0"
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AlreadyInstalledError",
|
|
28
|
+
"Arch",
|
|
29
|
+
"BrowsergetError",
|
|
30
|
+
"ChecksumMismatchError",
|
|
31
|
+
"Config",
|
|
32
|
+
"DriverMatchError",
|
|
33
|
+
"InsufficientDiskSpaceError",
|
|
34
|
+
"InstalledArtifact",
|
|
35
|
+
"NetworkError",
|
|
36
|
+
"OS",
|
|
37
|
+
"Platform",
|
|
38
|
+
"ResolvedVersion",
|
|
39
|
+
"SystemBrowser",
|
|
40
|
+
"UnknownTargetError",
|
|
41
|
+
"UnsupportedPlatformError",
|
|
42
|
+
"VersionNotFoundError",
|
|
43
|
+
"__version__",
|
|
44
|
+
"detect_platform",
|
|
45
|
+
"load_config",
|
|
46
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Safe archive extraction utilities with path-traversal protection.
|
|
2
|
+
|
|
3
|
+
All extraction functions validate that every entry in the archive resolves
|
|
4
|
+
to a path inside the destination directory, preventing Zip Slip and similar
|
|
5
|
+
path-traversal attacks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import stat
|
|
12
|
+
import sys
|
|
13
|
+
import tarfile
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _safe_extract_path(dest: Path, member_name: str) -> Path:
|
|
19
|
+
"""Resolve *member_name* relative to *dest* and verify it stays inside.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
dest: The destination directory.
|
|
23
|
+
member_name: The archive member's relative path.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The resolved, validated path.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If the resolved path escapes *dest*.
|
|
30
|
+
"""
|
|
31
|
+
dest_resolved = dest.resolve()
|
|
32
|
+
target = (dest / member_name).resolve()
|
|
33
|
+
try:
|
|
34
|
+
target.relative_to(dest_resolved)
|
|
35
|
+
except ValueError as exc:
|
|
36
|
+
raise ValueError(f"Archive entry '{member_name}' escapes destination directory") from exc
|
|
37
|
+
return target
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def extract_zip(archive_path: Path, dest: Path) -> None:
|
|
41
|
+
"""Extract a zip archive safely, preventing path traversal.
|
|
42
|
+
|
|
43
|
+
Validates both member paths and symlink targets to prevent
|
|
44
|
+
path-traversal attacks via malicious symlinks.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
archive_path: Path to the zip file.
|
|
48
|
+
dest: Destination directory (must exist).
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If any entry escapes the destination directory.
|
|
52
|
+
zipfile.BadZipFile: If the archive is corrupt or not a zip file.
|
|
53
|
+
"""
|
|
54
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
|
56
|
+
for member in zf.infolist():
|
|
57
|
+
_safe_extract_path(dest, member.filename)
|
|
58
|
+
unix_mode = member.external_attr >> 16
|
|
59
|
+
if stat.S_ISLNK(unix_mode):
|
|
60
|
+
link_target = zf.read(member).decode("utf-8", errors="replace")
|
|
61
|
+
_safe_extract_path(dest, link_target)
|
|
62
|
+
zf.extractall(dest)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def extract_tar(archive_path: Path, dest: Path) -> None:
|
|
66
|
+
"""Extract a tar archive safely, preventing path traversal.
|
|
67
|
+
|
|
68
|
+
Automatically detects compression (gzip, bzip2, xz, or uncompressed).
|
|
69
|
+
Validates both member paths and symlink/hardlink targets to prevent
|
|
70
|
+
path-traversal attacks via malicious symlinks.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
archive_path: Path to the tar file.
|
|
74
|
+
dest: Destination directory (must exist).
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If any entry escapes the destination directory.
|
|
78
|
+
tarfile.TarError: If the archive is corrupt.
|
|
79
|
+
"""
|
|
80
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
with tarfile.open(str(archive_path), "r:*") as tf:
|
|
82
|
+
for member in tf.getmembers():
|
|
83
|
+
_safe_extract_path(dest, member.name)
|
|
84
|
+
if member.issym() or member.islnk():
|
|
85
|
+
_safe_extract_path(dest, member.linkname)
|
|
86
|
+
if sys.version_info >= (3, 12):
|
|
87
|
+
tf.extractall(dest, filter="data")
|
|
88
|
+
else:
|
|
89
|
+
for member in tf.getmembers():
|
|
90
|
+
if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
|
|
91
|
+
raise ValueError(f"Refusing to extract special file: {member.name!r}")
|
|
92
|
+
tf.extractall(dest)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def find_file_by_name(root: Path, filename: str) -> Path | None:
|
|
96
|
+
"""Find a file by name in a directory tree without following symlinks.
|
|
97
|
+
|
|
98
|
+
Uses ``os.walk(followlinks=False)`` to avoid infinite recursion from
|
|
99
|
+
circular symlinks that may exist in extracted archives.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
root: The root directory to search.
|
|
103
|
+
filename: The filename to match (basename only).
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The first matching file path, or ``None`` if not found.
|
|
107
|
+
"""
|
|
108
|
+
for dirpath, _dirnames, filenames in os.walk(root, followlinks=False):
|
|
109
|
+
for fname in filenames:
|
|
110
|
+
if fname == filename:
|
|
111
|
+
candidate = Path(dirpath) / fname
|
|
112
|
+
if not candidate.is_symlink() and candidate.is_file():
|
|
113
|
+
return candidate
|
|
114
|
+
return None
|