Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
version: 2
updates:
# Keep GitHub Actions pinned to immutable commit SHAs up-to-date.
# Dependabot opens a PR whenever a newer SHA is available for a pinned action.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "dependencies"

# Keep Python runtime dependencies up-to-date within the bounded ranges in
# pyproject.toml [project.dependencies] (requirements.txt must stay in sync).
# Dependabot opens a PR when a newer version fits those bounds — it does NOT
# refresh requirements-lock.txt. CI installs from the lock, so after merging a
# Dependabot pip PR you must regenerate the lock: run the "Update dependency
# lock file" workflow (Actions tab) or pip-compile locally (see README).
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "dependencies"
71 changes: 66 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,61 @@ concurrency:
cancel-in-progress: true

jobs:
# ── Lock file + requirements sync (closes #47) ───────────────────────────
lockfile:
name: Lock file freshness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Verify requirements.txt matches pyproject.toml
run: |
python <<'PY'
import sys
import tomllib
from pathlib import Path

with open("pyproject.toml", "rb") as f:
py_deps = tomllib.load(f)["project"]["dependencies"]
req_deps = [
line.strip()
for line in Path("requirements.txt").read_text().splitlines()
if line.strip() and not line.strip().startswith("#")
]
if sorted(py_deps) != sorted(req_deps):
print(
"requirements.txt [project.dependencies] drift from pyproject.toml",
file=sys.stderr,
)
print("pyproject.toml:", sorted(py_deps), file=sys.stderr)
print("requirements.txt:", sorted(req_deps), 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'

- name: Verify requirements-lock.txt is up to date
# Same pip-compile flags as update-lock.yml, without --upgrade.
run: |
pip-compile requirements.txt \
--output-file /tmp/requirements-lock.txt \
--no-header \
--annotation-style=line \
--allow-unsafe \
--quiet
diff -u \
<(grep -E '^[A-Za-z0-9_.-]+==' requirements-lock.txt | sort) \
<(grep -E '^[A-Za-z0-9_.-]+==' /tmp/requirements-lock.txt | sort)

# ── Unit tests: matrix across OS and Python version ───────────────────────
# Closes #13. The unittest suite is the merge gate. Multi-OS catches the
# rare path / line-ending issue that a single-OS run hides; multi-Python
Expand All @@ -41,12 +96,15 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install runtime + test dependencies
# Only what the tests actually exercise. `pywebview` from
# requirements.txt is the desktop-launcher dep and pulls GTK / Qt
# system packages on Linux — out of scope for the unittest suite.
# Install from the pinned lock file for deterministic dependency
# resolution (closes #47). pytest is added on top — it is not in
# requirements-lock.txt because it is a dev-only dep. pywebview is
# the desktop-launcher dep and pulls GTK / Qt system libraries on
# Linux — intentionally excluded from the CI unittest matrix.
run: |
python -m pip install --upgrade pip
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'pytest>=8'
python -m pip install -r requirements-lock.txt
python -m pip install 'pytest>=8,<9'

- name: Run unittest suite
run: python -m unittest discover tests -v
Expand Down Expand Up @@ -78,9 +136,12 @@ jobs:
python-version: "3.12"

- name: Install runtime deps + mypy
# Install from the pinned lock file for deterministic resolution,
# then add mypy (dev-only; not in requirements-lock.txt).
run: |
python -m pip install --upgrade pip
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10'
python -m pip install -r requirements-lock.txt
python -m pip install 'mypy>=1.10,<2'

- name: Run mypy
# No `continue-on-error` — mypy now exits zero on this repo (closes #29),
Expand Down
74 changes: 74 additions & 0 deletions .github/workflows/update-lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Update dependency lock file

on:
# Run every Monday at 08:00 UTC — picks up upstream patch / security
# releases that land within the bounded ranges in requirements.txt.
schedule:
- cron: "0 8 * * 1"
# Allow manual trigger from the Actions tab for ad-hoc refreshes.
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
update-lock:
name: Regenerate requirements-lock.txt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Install pip-tools
# Pin matches tests.yml lockfile job so lock generation and verification agree.
run: python -m pip install 'pip-tools==7.5.3'

- name: Regenerate lock file
run: |
pip-compile requirements.txt \
--output-file requirements-lock.txt \
--no-header \
--annotation-style=line \
--allow-unsafe \
--upgrade

- name: Prepend lock file header
# pip-compile --no-header strips our docs header every run; restore via
# heredoc (single-quoted HEADER=... would leave literal \n characters).
run: |
cat > /tmp/lock-header <<'EOF'
# Pinned lock file — generated by pip-compile (pip-tools).
# Install: pip install -r requirements-lock.txt
# Update: pip-compile requirements.txt --output-file requirements-lock.txt --no-header --annotation-style=line --allow-unsafe --upgrade
# Run periodically (e.g. via the "Update dependency lock file" CI workflow) to pick up
# upstream patch / security releases within the bounded ranges in requirements.txt.
EOF
cat /tmp/lock-header requirements-lock.txt > /tmp/lock.tmp
mv /tmp/lock.tmp requirements-lock.txt

- name: Open PR if lock file changed
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
with:
commit-message: "chore: update requirements-lock.txt"
branch: "chore/update-lock-file"
delete-branch: true
title: "chore: update dependency lock file"
body: |
Automated weekly refresh of `requirements-lock.txt`.

Generated by `pip-compile --upgrade` from the bounded specifiers
in `requirements.txt` (must match `pyproject.toml` `[project.dependencies]`).

**Dependabot pip PRs** may bump bounds in `requirements.txt` / `pyproject.toml`
but do not regenerate this lock file — merge those first, then merge this PR
(or run **Actions → Update dependency lock file → Run workflow**).

Review the diff to confirm no unexpected major-version jumps before merging.
labels: dependencies
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,36 @@ source venv/bin/activate
pip install -r requirements.txt
```

For reproducible installs (same versions as CI), use the pinned lock file:

```bash
pip install -r requirements-lock.txt
```

### Dependency bounds and lock file

Runtime version **bounds** live in `pyproject.toml` under `[project.dependencies]` (`flask`, `fpdf2`, `pillow`, etc.). `requirements.txt` mirrors those specifiers for backward compatibility — keep them identical when you change deps.

**CI** installs from `requirements-lock.txt`, which pins exact versions (including transitive packages). The lock is produced on **Linux** (same as CI and `update-lock.yml`); `pip-compile` on Windows may add platform-only pins such as `colorama` — do not commit those.

Regenerate after editing bounds (prefer **Actions → Update dependency lock file → Run workflow**, or on Linux / WSL):

```bash
pip install pip-tools
pip-compile requirements.txt \
--output-file requirements-lock.txt \
--no-header \
--annotation-style=line \
--allow-unsafe
```

Then restore the comment header at the top of `requirements-lock.txt` (see the existing file) and commit both `requirements.txt` / `pyproject.toml` and `requirements-lock.txt`.

**Automated updates:**

- **Dependabot** (`.github/dependabot.yml`) — weekly PRs for `pip` and `github-actions` when newer versions fit the declared bounds. Merging a Dependabot **pip** PR does **not** refresh the lock file; run the lock workflow or `pip-compile` locally afterward.
- **Update dependency lock file** (`.github/workflows/update-lock.yml`) — scheduled Mondays 08:00 UTC (and manual **Actions → Run workflow**) runs `pip-compile --upgrade` and opens a PR with an updated `requirements-lock.txt`.

## Quick Start (Web UI)

```bash
Expand All @@ -73,7 +103,7 @@ The Werkzeug debugger is **off by default** and must be opted in explicitly via

## Tests

Run the full suite from the repository root (install `requirements.txt` first):
Run the full suite from the repository root (install `requirements-lock.txt` or `requirements.txt` first):

```bash
python -m unittest discover tests -v
Expand Down Expand Up @@ -147,7 +177,9 @@ Cursor CLI agent sessions are read from `~/.cursor/chats/` (the default path use
```
cursor-chat-browser-python/
├── app.py # Flask application entry point
├── requirements.txt # Python dependencies
├── requirements.txt # Runtime bounds (mirrors pyproject.toml)
├── requirements-lock.txt # Pinned lock file used by CI
├── pyproject.toml # Package metadata and canonical dependency bounds
├── api/ # API route blueprints
│ ├── workspaces.py # /api/workspaces endpoints
│ ├── composers.py # /api/composers endpoints
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ requires-python = ">=3.10"
dependencies = [
"flask>=3.0,<4",
"fpdf2>=2.7,<3",
# Security floor: fpdf2 allows Pillow>=8.3.2, so 9.x can still be resolved.
# CVE-2024-28219 (buffer overflow) fixed in Pillow 10.3.0 — https://nvd.nist.gov/vuln/detail/CVE-2024-28219
"pillow>=10.3.0",
# Security floor: fpdf2 allows Pillow>=8.3.2 (no upper cap); pin 12.x to avoid
# known high-severity CVEs in Pillow 10.x (e.g. CVE-2024-28219 and later advisories).
"pillow>=12.2.0,<13",
]

[project.optional-dependencies]
Expand Down
18 changes: 18 additions & 0 deletions requirements-lock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Pinned lock file — generated by pip-compile (pip-tools).
# Install: pip install -r requirements-lock.txt
# Update: pip-compile requirements.txt --output-file requirements-lock.txt --no-header --annotation-style=line --allow-unsafe --upgrade
# Run periodically (e.g. via the "Update dependency lock file" CI workflow) to pick up
# upstream patch / security releases within the bounded ranges in requirements.txt.
# Lock is generated on Linux (CI / update-lock.yml). Windows-only transitives (e.g.
# colorama via click) are omitted — pip still installs them on Windows when needed.
blinker==1.9.0 # via flask
click==8.4.0 # via flask
defusedxml==0.7.1 # via fpdf2
flask==3.1.3 # via -r requirements.txt
fonttools==4.63.0 # via fpdf2
fpdf2==2.8.7 # via -r requirements.txt
itsdangerous==2.2.0 # via flask
jinja2==3.1.6 # via flask
markupsafe==3.0.3 # via flask, jinja2, werkzeug
pillow==12.2.0 # via -r requirements.txt, fpdf2
werkzeug==3.1.8 # via flask
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
# pip install -e ".[desktop]" (+ pywebview for the GUI launcher)
flask>=3.0,<4
fpdf2>=2.7,<3
pillow>=10.3.0
pillow>=12.2.0,<13
# pywebview is desktop-only — install with: pip install -e ".[desktop]"
Loading