From 94aa46bb86e907931dd0e4a9111070e8de7242f4 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 30 Jul 2026 16:17:09 +0800 Subject: [PATCH 1/7] feat: add tag-triggered release workflow for wheel, sdist and Windows bundle --- .github/workflows/release.yml | 120 ++++++++++++++++++++++++++++++++++ README.md | 12 ++++ cursor-browser.spec | 1 + 3 files changed, 133 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c5a5b25 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,120 @@ +name: Release + +# Fires when an annotated version tag is pushed (for example v0.2.1). +# Builds hatchling wheel + sdist and a Windows PyInstaller zip, then attaches +# all three as GitHub Release assets via softprops/action-gh-release. +# +# Expected asset names (version comes from pyproject.toml at the tagged commit): +# cppa_cursor_browser--py3-none-any.whl (~154 KiB at 0.2.0) +# cppa_cursor_browser-.tar.gz (~225 KiB at 0.2.0) +# CursorChatBrowser-windows.zip (PyInstaller onedir bundle) +# +# Fork verification: push a test tag (for example v0.0.0-test) and confirm the +# workflow attaches all three files to the resulting Release. + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + build-python: + name: Build wheel and sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Build hatchling distributables + run: | + python -m pip install --upgrade pip + python -m pip install 'build>=1,<2' + python -m build + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: python-distributables + path: | + dist/*.whl + dist/*.tar.gz + if-no-files-found: error + + build-windows: + name: Build Windows PyInstaller bundle + runs-on: windows-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Install runtime dependencies + # Lock file for Flask/runtime deps; pywebview is the [desktop] extra and + # is required so the published exe can open its native window. + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-lock.txt + python -m pip install 'pywebview>=5.0,<7' + + - name: Install PyInstaller + run: python -m pip install 'pyinstaller>=6,<7' + + - name: Build PyInstaller bundle + run: pyinstaller cursor-browser.spec --noconfirm + + - name: Smoke-test PyInstaller exe (--help) + run: dist\CursorChatBrowser\CursorChatBrowser.exe --help + + - name: Zip onedir bundle + shell: pwsh + run: | + if (-not (Test-Path dist\CursorChatBrowser\CursorChatBrowser.exe)) { + Write-Error "dist\CursorChatBrowser\CursorChatBrowser.exe not found" + exit 1 + } + Compress-Archive -Path dist\CursorChatBrowser -DestinationPath CursorChatBrowser-windows.zip + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: windows-bundle + path: CursorChatBrowser-windows.zip + if-no-files-found: error + + publish: + name: Publish GitHub Release assets + needs: [build-python, build-windows] + runs-on: ubuntu-latest + steps: + - name: Download Python distributables + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: python-distributables + path: release-assets + + - name: Download Windows bundle + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: windows-bundle + path: release-assets + + - name: Attach artifacts to Release + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 + with: + files: release-assets/* diff --git a/README.md b/README.md index d96b429..d7febcc 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,18 @@ Adding new optional fields to JSON responses, adding new CLI flags with sensible Notable changes will be documented in **[CHANGELOG.md](CHANGELOG.md)** following the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. +### GitHub Releases + +Pushing an annotated tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release: + +| Asset | Contents | +|---|---| +| `cppa_cursor_browser--py3-none-any.whl` | Installable wheel (hatchling build) | +| `cppa_cursor_browser-.tar.gz` | Source distribution | +| `CursorChatBrowser-windows.zip` | Windows PyInstaller onedir bundle (`CursorChatBrowser.exe` plus supporting files) | + +At `0.2.0`, local `python -m build` produced a ~154 KiB wheel and ~225 KiB sdist; the Windows zip size depends on the locked dependency tree at tag time. Copy release notes from the matching `[version]` section in `CHANGELOG.md` when publishing the GitHub Release. + When an API surface is scheduled for removal, follow the process in **[docs/API_DEPRECATION.md](docs/API_DEPRECATION.md)** (response headers, changelog entries, minimum notice period). ## License diff --git a/cursor-browser.spec b/cursor-browser.spec index 6d18f8e..1e0a1f3 100644 --- a/cursor-browser.spec +++ b/cursor-browser.spec @@ -17,6 +17,7 @@ a = Analysis( (str(src / "static"), "static"), ], hiddenimports=[ + "webview", "api.workspaces", "api.composers", "api.logs", From 0571377d092b6539dbf8fa9694ab9037a60d96c9 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 30 Jul 2026 16:32:41 +0800 Subject: [PATCH 2/7] ci: limit release contents write to publish job and fix tag docs --- .github/workflows/release.yml | 6 ++++-- README.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5a5b25..4a51d3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ name: Release -# Fires when an annotated version tag is pushed (for example v0.2.1). +# Fires when a version tag is pushed (for example v0.2.1). # Builds hatchling wheel + sdist and a Windows PyInstaller zip, then attaches # all three as GitHub Release assets via softprops/action-gh-release. # @@ -18,7 +18,7 @@ on: - "v*" permissions: - contents: write + contents: read concurrency: group: release-${{ github.ref }} @@ -101,6 +101,8 @@ jobs: name: Publish GitHub Release assets needs: [build-python, build-windows] runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Download Python distributables uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 diff --git a/README.md b/README.md index d7febcc..f5d5056 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ Notable changes will be documented in **[CHANGELOG.md](CHANGELOG.md)** following ### GitHub Releases -Pushing an annotated tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release: +Pushing a tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release: | Asset | Contents | |---|---| From 8b8eca27f8874672877e0a05c5458a3bc72b0217 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 30 Jul 2026 21:31:39 +0800 Subject: [PATCH 3/7] Address brad's feedback --- .github/workflows/release.yml | 13 +++++++++++++ .github/workflows/tests.yml | 5 +++++ README.md | 2 +- cursor-browser.spec | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a51d3b..4d1c661 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,13 @@ name: Release # # Fork verification: push a test tag (for example v0.0.0-test) and confirm the # workflow attaches all three files to the resulting Release. +# workflow_dispatch runs build-python + build-windows only (publish is tag-only). on: push: tags: - "v*" + workflow_dispatch: permissions: contents: read @@ -100,6 +102,7 @@ jobs: publish: name: Publish GitHub Release assets needs: [build-python, build-windows] + if: github.ref_type == 'tag' runs-on: ubuntu-latest permissions: contents: write @@ -116,6 +119,16 @@ jobs: name: windows-bundle path: release-assets + - name: Verify release asset set + run: | + set -euo pipefail + ls -la release-assets/ + count=$(find release-assets -maxdepth 1 -type f | wc -l) + test "$count" -eq 3 + test "$(find release-assets -maxdepth 1 -type f -name '*.whl' | wc -l)" -eq 1 + test "$(find release-assets -maxdepth 1 -type f -name '*.tar.gz' | wc -l)" -eq 1 + test -f release-assets/CursorChatBrowser-windows.zip + - name: Attach artifacts to Release uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9ce023b..654ef26 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -120,6 +120,11 @@ jobs: # ── PyInstaller desktop build (Windows only, once per workflow) ──────── # Closes #44. Builds the onedir bundle and smoke-tests --help so the # desktop entry point is verified without launching the GUI window. + # pywebview matches release.yml so CI bundles the same graph we ship. + - name: Install pywebview for PyInstaller bundle + if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' + run: python -m pip install 'pywebview>=5.0,<7' + - name: Install PyInstaller if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' run: python -m pip install 'pyinstaller>=6,<7' diff --git a/README.md b/README.md index f5d5056..3e7620e 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ Notable changes will be documented in **[CHANGELOG.md](CHANGELOG.md)** following ### GitHub Releases -Pushing a tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release: +Pushing a tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release. Set `[project].version` in `pyproject.toml` to match the tag before you push (for example tag `v0.2.1` requires `version = "0.2.1"`); hatchling names the wheel and sdist from pyproject, not from the git tag. | Asset | Contents | |---|---| diff --git a/cursor-browser.spec b/cursor-browser.spec index 1e0a1f3..4c02d9c 100644 --- a/cursor-browser.spec +++ b/cursor-browser.spec @@ -17,7 +17,7 @@ a = Analysis( (str(src / "static"), "static"), ], hiddenimports=[ - "webview", + "webview", # pywebview must be installed at build time; hiddenimport alone is not enough "api.workspaces", "api.composers", "api.logs", From 319d6da76ad07c8c3dea5807e8e1457d46b4a5a4 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 30 Jul 2026 21:40:07 +0800 Subject: [PATCH 4/7] add whl, tar.gz test more --- .github/workflows/release.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d1c661..f2a30f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: publish: name: Publish GitHub Release assets needs: [build-python, build-windows] - if: github.ref_type == 'tag' + if: github.event_name == 'push' && github.ref_type == 'tag' runs-on: ubuntu-latest permissions: contents: write @@ -120,13 +120,16 @@ jobs: path: release-assets - name: Verify release asset set + env: + RELEASE_TAG: ${{ github.ref_name }} run: | set -euo pipefail + version="${RELEASE_TAG#v}" ls -la release-assets/ count=$(find release-assets -maxdepth 1 -type f | wc -l) test "$count" -eq 3 - test "$(find release-assets -maxdepth 1 -type f -name '*.whl' | wc -l)" -eq 1 - test "$(find release-assets -maxdepth 1 -type f -name '*.tar.gz' | wc -l)" -eq 1 + test -f "release-assets/cppa_cursor_browser-${version}-py3-none-any.whl" + test -f "release-assets/cppa_cursor_browser-${version}.tar.gz" test -f release-assets/CursorChatBrowser-windows.zip - name: Attach artifacts to Release From ae0c38b9ad99c058874692d3c710bfe40fd531ae Mon Sep 17 00:00:00 2001 From: star-med Date: Fri, 31 Jul 2026 02:50:46 +0800 Subject: [PATCH 5/7] clean up content --- .github/workflows/release.yml | 18 +++++++----------- .github/workflows/tests.yml | 2 +- README.md | 4 ++-- cursor-browser.spec | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2a30f9..1bed5ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,17 +1,14 @@ name: Release -# Fires when a version tag is pushed (for example v0.2.1). -# Builds hatchling wheel + sdist and a Windows PyInstaller zip, then attaches -# all three as GitHub Release assets via softprops/action-gh-release. +# v* tag push: build wheel, sdist, and Windows zip; publish attaches all three to the Release. +# workflow_dispatch runs the build jobs only (no publish). # -# Expected asset names (version comes from pyproject.toml at the tagged commit): +# Asset names (version from pyproject.toml at the tagged commit): # cppa_cursor_browser--py3-none-any.whl (~154 KiB at 0.2.0) -# cppa_cursor_browser-.tar.gz (~225 KiB at 0.2.0) -# CursorChatBrowser-windows.zip (PyInstaller onedir bundle) +# cppa_cursor_browser-.tar.gz (~225 KiB at 0.2.0) +# CursorChatBrowser-windows.zip # -# Fork verification: push a test tag (for example v0.0.0-test) and confirm the -# workflow attaches all three files to the resulting Release. -# workflow_dispatch runs build-python + build-windows only (publish is tag-only). +# Fork check: push v0.0.0-test and confirm all three files on the Release. on: push: @@ -68,8 +65,7 @@ jobs: python-version: "3.12" - name: Install runtime dependencies - # Lock file for Flask/runtime deps; pywebview is the [desktop] extra and - # is required so the published exe can open its native window. + # requirements-lock.txt for runtime; pywebview for the desktop window. run: | python -m pip install --upgrade pip python -m pip install -r requirements-lock.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 654ef26..55f0826 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -120,7 +120,7 @@ jobs: # ── PyInstaller desktop build (Windows only, once per workflow) ──────── # Closes #44. Builds the onedir bundle and smoke-tests --help so the # desktop entry point is verified without launching the GUI window. - # pywebview matches release.yml so CI bundles the same graph we ship. + # pywebview matches release.yml so the Windows bundle matches what we ship. - name: Install pywebview for PyInstaller bundle if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' run: python -m pip install 'pywebview>=5.0,<7' diff --git a/README.md b/README.md index 3e7620e..e299d74 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ Notable changes will be documented in **[CHANGELOG.md](CHANGELOG.md)** following ### GitHub Releases -Pushing a tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which attaches three assets to the GitHub Release. Set `[project].version` in `pyproject.toml` to match the tag before you push (for example tag `v0.2.1` requires `version = "0.2.1"`); hatchling names the wheel and sdist from pyproject, not from the git tag. +Push a `v*` tag (for example `v0.2.1`) to run [`.github/workflows/release.yml`](.github/workflows/release.yml). It uploads three assets to the GitHub Release. Set `[project].version` in `pyproject.toml` to match the tag before you push (`v0.2.1` needs `version = "0.2.1"`). Hatchling names the wheel and sdist from pyproject, not the git tag. | Asset | Contents | |---|---| @@ -273,7 +273,7 @@ Pushing a tag matching `v*` (for example `v0.2.1`) runs [`.github/workflows/rele | `cppa_cursor_browser-.tar.gz` | Source distribution | | `CursorChatBrowser-windows.zip` | Windows PyInstaller onedir bundle (`CursorChatBrowser.exe` plus supporting files) | -At `0.2.0`, local `python -m build` produced a ~154 KiB wheel and ~225 KiB sdist; the Windows zip size depends on the locked dependency tree at tag time. Copy release notes from the matching `[version]` section in `CHANGELOG.md` when publishing the GitHub Release. +At `0.2.0`, a local `python -m build` gave a ~154 KiB wheel and ~225 KiB sdist. Windows zip size varies with the locked tree at tag time. Paste release notes from the matching `[version]` section in `CHANGELOG.md`. When an API surface is scheduled for removal, follow the process in **[docs/API_DEPRECATION.md](docs/API_DEPRECATION.md)** (response headers, changelog entries, minimum notice period). diff --git a/cursor-browser.spec b/cursor-browser.spec index 4c02d9c..bf66db0 100644 --- a/cursor-browser.spec +++ b/cursor-browser.spec @@ -17,7 +17,7 @@ a = Analysis( (str(src / "static"), "static"), ], hiddenimports=[ - "webview", # pywebview must be installed at build time; hiddenimport alone is not enough + "webview", # needs pywebview installed at build time "api.workspaces", "api.composers", "api.logs", From aa85b930ede21f15896664e4f190f6116371bb6f Mon Sep 17 00:00:00 2001 From: star-med Date: Fri, 31 Jul 2026 04:22:33 +0800 Subject: [PATCH 6/7] ci: enforce tag version match and pywebview bundle checks --- .github/workflows/release.yml | 31 +++++++++++++++++---- .github/workflows/tests.yml | 37 +++++++++++++++++++++----- scripts/read_desktop_pywebview_spec.py | 21 +++++++++++++++ 3 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 scripts/read_desktop_pywebview_spec.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bed5ff..2822466 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ name: Release # cppa_cursor_browser-.tar.gz (~225 KiB at 0.2.0) # CursorChatBrowser-windows.zip # -# Fork check: push v0.0.0-test and confirm all three files on the Release. +# Fork rehearsal: push v0.2.0 (pyproject is already 0.2.0) and confirm all three assets on the Release. on: push: @@ -37,6 +37,19 @@ jobs: with: python-version: "3.12" + - name: Tag must match pyproject version + if: github.ref_type == 'tag' + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + tag_version="${RELEASE_TAG#v}" + pyproject_version="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")" + if [ "$tag_version" != "$pyproject_version" ]; then + echo "Release tag is $tag_version but pyproject.toml [project].version is $pyproject_version" + exit 1 + fi + - name: Build hatchling distributables run: | python -m pip install --upgrade pip @@ -65,11 +78,13 @@ jobs: python-version: "3.12" - name: Install runtime dependencies - # requirements-lock.txt for runtime; pywebview for the desktop window. + # Runtime from requirements-lock.txt; pywebview pin from pyproject [desktop]. + shell: pwsh run: | python -m pip install --upgrade pip python -m pip install -r requirements-lock.txt - python -m pip install 'pywebview>=5.0,<7' + $spec = python scripts/read_desktop_pywebview_spec.py + python -m pip install $spec - name: Install PyInstaller run: python -m pip install 'pyinstaller>=6,<7' @@ -77,8 +92,14 @@ jobs: - name: Build PyInstaller bundle run: pyinstaller cursor-browser.spec --noconfirm - - name: Smoke-test PyInstaller exe (--help) - run: dist\CursorChatBrowser\CursorChatBrowser.exe --help + - name: Check webview/lib in bundle + shell: pwsh + run: | + $lib = 'dist\CursorChatBrowser\_internal\webview\lib' + if (-not (Test-Path $lib)) { + Write-Error "pywebview bundle missing: $lib" + exit 1 + } - name: Zip onedir bundle shell: pwsh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 55f0826..66a07d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,6 +54,24 @@ jobs: sys.exit(1) PY + - name: Check pywebview pin is single-sourced + run: | + python scripts/read_desktop_pywebview_spec.py + python <<'PY' + import sys + from pathlib import Path + + for path in sorted(Path(".github/workflows").glob("*.yml")): + text = path.read_text() + if "pywebview>=" in text or "pywebview<" in text: + print(f"{path} hardcodes a pywebview version; use scripts/read_desktop_pywebview_spec.py", file=sys.stderr) + sys.exit(1) + if "read_desktop_pywebview_spec.py" not in text and "pywebview" in text.lower(): + if path.name in ("release.yml", "tests.yml"): + print(f"{path} installs pywebview but does not read the pin from pyproject", file=sys.stderr) + sys.exit(1) + PY + - name: Install pip-tools # Pin matches update-lock.yml so lock verification uses the same resolver. run: python -m pip install 'pip-tools==7.5.3' @@ -118,12 +136,13 @@ jobs: run: python -m pytest tests/test_api_search.py tests/test_api_workspaces.py tests/test_api_export.py tests/test_pdf_export.py tests/test_search_helpers.py tests/test_check_benchmark_regression.py tests/test_reduce_baselines.py -v --tb=short -o addopts= # ── PyInstaller desktop build (Windows only, once per workflow) ──────── - # Closes #44. Builds the onedir bundle and smoke-tests --help so the - # desktop entry point is verified without launching the GUI window. - # pywebview matches release.yml so the Windows bundle matches what we ship. + # Closes #44. Check webview/lib after build; --help returns before import webview. - name: Install pywebview for PyInstaller bundle if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' - run: python -m pip install 'pywebview>=5.0,<7' + shell: pwsh + run: | + $spec = python scripts/read_desktop_pywebview_spec.py + python -m pip install $spec - name: Install PyInstaller if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' @@ -133,9 +152,15 @@ jobs: if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' run: pyinstaller cursor-browser.spec --noconfirm - - name: Smoke-test PyInstaller exe (--help) + - name: Check webview/lib in bundle if: matrix.os == 'windows-latest' && matrix.python-version == '3.12' - run: dist\CursorChatBrowser\CursorChatBrowser.exe --help + shell: pwsh + run: | + $lib = 'dist\CursorChatBrowser\_internal\webview\lib' + if (-not (Test-Path $lib)) { + Write-Error "pywebview bundle missing: $lib" + exit 1 + } # ── Browser XSS: Playwright (sprint item #3) ───────────────────────────── browser-xss: diff --git a/scripts/read_desktop_pywebview_spec.py b/scripts/read_desktop_pywebview_spec.py new file mode 100644 index 0000000..f45d95d --- /dev/null +++ b/scripts/read_desktop_pywebview_spec.py @@ -0,0 +1,21 @@ +"""Stdout the pywebview pin from pyproject.toml [desktop].""" + +from __future__ import annotations + +import sys +import tomllib + + +def main() -> None: + deps = tomllib.load(open("pyproject.toml", "rb"))["project"]["optional-dependencies"]["desktop"] + if len(deps) != 1 or not deps[0].startswith("pywebview"): + print( + "need exactly one pywebview dep in [project.optional-dependencies].desktop", + file=sys.stderr, + ) + raise SystemExit(1) + print(deps[0]) + + +if __name__ == "__main__": + main() From 4b20d953b261df858875fc0e9b61446e22481dd8 Mon Sep 17 00:00:00 2001 From: star-med Date: Fri, 31 Jul 2026 04:28:37 +0800 Subject: [PATCH 7/7] moving guard from inline in tests.yml --- .github/workflows/tests.yml | 15 +----------- scripts/check_pywebview_workflow_pin.py | 31 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 scripts/check_pywebview_workflow_pin.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66a07d1..8d5db70 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,20 +57,7 @@ jobs: - name: Check pywebview pin is single-sourced run: | python scripts/read_desktop_pywebview_spec.py - python <<'PY' - import sys - from pathlib import Path - - for path in sorted(Path(".github/workflows").glob("*.yml")): - text = path.read_text() - if "pywebview>=" in text or "pywebview<" in text: - print(f"{path} hardcodes a pywebview version; use scripts/read_desktop_pywebview_spec.py", file=sys.stderr) - sys.exit(1) - if "read_desktop_pywebview_spec.py" not in text and "pywebview" in text.lower(): - if path.name in ("release.yml", "tests.yml"): - print(f"{path} installs pywebview but does not read the pin from pyproject", file=sys.stderr) - sys.exit(1) - PY + python scripts/check_pywebview_workflow_pin.py - name: Install pip-tools # Pin matches update-lock.yml so lock verification uses the same resolver. diff --git a/scripts/check_pywebview_workflow_pin.py b/scripts/check_pywebview_workflow_pin.py new file mode 100644 index 0000000..01ecb01 --- /dev/null +++ b/scripts/check_pywebview_workflow_pin.py @@ -0,0 +1,31 @@ +"""Fail if workflow YAML hardcodes a pywebview version instead of read_desktop_pywebview_spec.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +READ_SCRIPT = "read_desktop_pywebview_spec.py" +PYWEBVIEW_INSTALL_WORKFLOWS = ("release.yml", "tests.yml") + + +def main() -> None: + for path in sorted(Path(".github/workflows").glob("*.yml")): + text = path.read_text() + if "pywebview>=" in text or "pywebview<" in text: + print( + f"{path} hardcodes a pywebview version; use scripts/{READ_SCRIPT}", + file=sys.stderr, + ) + raise SystemExit(1) + if READ_SCRIPT not in text and "pywebview" in text.lower(): + if path.name in PYWEBVIEW_INSTALL_WORKFLOWS: + print( + f"{path} installs pywebview but does not read the pin from pyproject", + file=sys.stderr, + ) + raise SystemExit(1) + + +if __name__ == "__main__": + main()