diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49e2d61..a3ea9df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] workflow_dispatch: +permissions: + contents: read + jobs: test: name: pytest + ruff + mypy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c65db8c..529b9cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: tags: - "v*.*.*" +permissions: + contents: read + jobs: publish: name: build + publish wheel + bundle @@ -32,7 +35,7 @@ jobs: rm -rf dist/__pycache__ - name: Upload to PyPI (only whl + sdist) - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 # At this point dist/ contains exactly *.whl + *.tar.gz. The # bundle + checksum file are written to bundle/ in the next # step so twine doesn't try to upload them. @@ -44,7 +47,7 @@ jobs: > bundle/SHA256SUMS - name: Attach bundle + checksums to GitHub Release - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: files: | bundle/installer.py diff --git a/src/get_installer/__main__.py b/src/get_installer/__main__.py index f90db96..759ca63 100644 --- a/src/get_installer/__main__.py +++ b/src/get_installer/__main__.py @@ -194,6 +194,10 @@ def main(argv: list[str] | None = None) -> int: pre = Registry.load(fallback) allowed_origins = pre.access_control.allowed_origins except ConfigError: + # Deliberate: an unreadable local registry is treated + # like a missing one -- no allowed_origins pre-load. The + # URL fetch still runs; from_url falls back to this path + # only on fetch failure and raises if it is unusable too. pass registry = Registry.from_url( registry_arg, diff --git a/tests/test_bootstrap_launchers.py b/tests/test_bootstrap_launchers.py index ba6386a..bb6a6ad 100644 --- a/tests/test_bootstrap_launchers.py +++ b/tests/test_bootstrap_launchers.py @@ -14,12 +14,14 @@ import http.server import os +import re import shutil import socketserver import subprocess import sys import threading from pathlib import Path +from urllib.parse import urlsplit import pytest @@ -207,10 +209,16 @@ def test_install_sh_lists_install_urls_when_no_python(tmp_path: Path) -> None: ) assert r.returncode != 0 combined = (r.stdout + r.stderr).lower() - # All three escape hatches must be named with their URL host - assert "docs.astral.sh/uv" in combined - assert "pipx.pypa.io" in combined - assert "python.org" in combined + # All three escape hatches must be named with their URL host. Parse + # the printed URLs and compare hosts exactly, rather than matching a + # substring that could sit anywhere in a URL. + urls = [urlsplit(u) for u in re.findall(r"https?://\S+", combined)] + assert any( + u.hostname == "docs.astral.sh" and u.path.startswith("/uv") + for u in urls + ) + assert any(u.hostname == "pipx.pypa.io" for u in urls) + assert any(u.hostname == "www.python.org" for u in urls) assert "--bootstrap-uv" in combined