Skip to content

Commit 34aaedd

Browse files
authored
Feat: Unbounded Dependency Pins in requirements.txt (Closes #47) (#53)
* feat: initial implement with dependabot + test + update-lock actions. * fix: create-pull-request v7; pillow version to 12 * fix: review comments * fix issues after changing * refactor: eliminate remaining duplications from export pipeline Extract _load_bubble_map/_load_project_layouts_map/_load_code_block_diff_map to services/workspace_db.py; add cursor_ide_chat_to_markdown to utils/cursor_md_exporter.py; remove private _slug() copies from api/export_api.py and utils/cursor_md_exporter.py. * Revert "refactor: eliminate remaining duplications from export pipeline" This reverts commit 7926293.
1 parent 3894ee6 commit 34aaedd

7 files changed

Lines changed: 221 additions & 11 deletions

File tree

.github/dependabot.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: 2
2+
updates:
3+
# Keep GitHub Actions pinned to immutable commit SHAs up-to-date.
4+
# Dependabot opens a PR whenever a newer SHA is available for a pinned action.
5+
- package-ecosystem: "github-actions"
6+
directory: "/"
7+
schedule:
8+
interval: "weekly"
9+
day: "monday"
10+
labels:
11+
- "dependencies"
12+
13+
# Keep Python runtime dependencies up-to-date within the bounded ranges in
14+
# pyproject.toml [project.dependencies] (requirements.txt must stay in sync).
15+
# Dependabot opens a PR when a newer version fits those bounds — it does NOT
16+
# refresh requirements-lock.txt. CI installs from the lock, so after merging a
17+
# Dependabot pip PR you must regenerate the lock: run the "Update dependency
18+
# lock file" workflow (Actions tab) or pip-compile locally (see README).
19+
- package-ecosystem: "pip"
20+
directory: "/"
21+
schedule:
22+
interval: "weekly"
23+
day: "monday"
24+
labels:
25+
- "dependencies"

.github/workflows/tests.yml

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,61 @@ concurrency:
1616
cancel-in-progress: true
1717

1818
jobs:
19+
# ── Lock file + requirements sync (closes #47) ───────────────────────────
20+
lockfile:
21+
name: Lock file freshness
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
25+
with:
26+
persist-credentials: false
27+
28+
- name: Set up Python
29+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
30+
with:
31+
python-version: "3.12"
32+
33+
- name: Verify requirements.txt matches pyproject.toml
34+
run: |
35+
python <<'PY'
36+
import sys
37+
import tomllib
38+
from pathlib import Path
39+
40+
with open("pyproject.toml", "rb") as f:
41+
py_deps = tomllib.load(f)["project"]["dependencies"]
42+
req_deps = [
43+
line.strip()
44+
for line in Path("requirements.txt").read_text().splitlines()
45+
if line.strip() and not line.strip().startswith("#")
46+
]
47+
if sorted(py_deps) != sorted(req_deps):
48+
print(
49+
"requirements.txt [project.dependencies] drift from pyproject.toml",
50+
file=sys.stderr,
51+
)
52+
print("pyproject.toml:", sorted(py_deps), file=sys.stderr)
53+
print("requirements.txt:", sorted(req_deps), file=sys.stderr)
54+
sys.exit(1)
55+
PY
56+
57+
- name: Install pip-tools
58+
# Pin matches update-lock.yml so lock verification uses the same resolver.
59+
run: python -m pip install 'pip-tools==7.5.3'
60+
61+
- name: Verify requirements-lock.txt is up to date
62+
# Same pip-compile flags as update-lock.yml, without --upgrade.
63+
run: |
64+
pip-compile requirements.txt \
65+
--output-file /tmp/requirements-lock.txt \
66+
--no-header \
67+
--annotation-style=line \
68+
--allow-unsafe \
69+
--quiet
70+
diff -u \
71+
<(grep -E '^[A-Za-z0-9_.-]+==' requirements-lock.txt | sort) \
72+
<(grep -E '^[A-Za-z0-9_.-]+==' /tmp/requirements-lock.txt | sort)
73+
1974
# ── Unit tests: matrix across OS and Python version ───────────────────────
2075
# Closes #13. The unittest suite is the merge gate. Multi-OS catches the
2176
# rare path / line-ending issue that a single-OS run hides; multi-Python
@@ -41,12 +96,15 @@ jobs:
4196
python-version: ${{ matrix.python-version }}
4297

4398
- name: Install runtime + test dependencies
44-
# Only what the tests actually exercise. `pywebview` from
45-
# requirements.txt is the desktop-launcher dep and pulls GTK / Qt
46-
# system packages on Linux — out of scope for the unittest suite.
99+
# Install from the pinned lock file for deterministic dependency
100+
# resolution (closes #47). pytest is added on top — it is not in
101+
# requirements-lock.txt because it is a dev-only dep. pywebview is
102+
# the desktop-launcher dep and pulls GTK / Qt system libraries on
103+
# Linux — intentionally excluded from the CI unittest matrix.
47104
run: |
48105
python -m pip install --upgrade pip
49-
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'pytest>=8'
106+
python -m pip install -r requirements-lock.txt
107+
python -m pip install 'pytest>=8,<9'
50108
51109
- name: Run unittest suite
52110
run: python -m unittest discover tests -v
@@ -78,9 +136,12 @@ jobs:
78136
python-version: "3.12"
79137

80138
- name: Install runtime deps + mypy
139+
# Install from the pinned lock file for deterministic resolution,
140+
# then add mypy (dev-only; not in requirements-lock.txt).
81141
run: |
82142
python -m pip install --upgrade pip
83-
python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10'
143+
python -m pip install -r requirements-lock.txt
144+
python -m pip install 'mypy>=1.10,<2'
84145
85146
- name: Run mypy
86147
# No `continue-on-error` — mypy now exits zero on this repo (closes #29),

.github/workflows/update-lock.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Update dependency lock file
2+
3+
on:
4+
# Run every Monday at 08:00 UTC — picks up upstream patch / security
5+
# releases that land within the bounded ranges in requirements.txt.
6+
schedule:
7+
- cron: "0 8 * * 1"
8+
# Allow manual trigger from the Actions tab for ad-hoc refreshes.
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
15+
jobs:
16+
update-lock:
17+
name: Regenerate requirements-lock.txt
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
21+
with:
22+
persist-credentials: false
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
26+
with:
27+
python-version: "3.12"
28+
29+
- name: Install pip-tools
30+
# Pin matches tests.yml lockfile job so lock generation and verification agree.
31+
run: python -m pip install 'pip-tools==7.5.3'
32+
33+
- name: Regenerate lock file
34+
run: |
35+
pip-compile requirements.txt \
36+
--output-file requirements-lock.txt \
37+
--no-header \
38+
--annotation-style=line \
39+
--allow-unsafe \
40+
--upgrade
41+
42+
- name: Prepend lock file header
43+
# pip-compile --no-header strips our docs header every run; restore via
44+
# heredoc (single-quoted HEADER=... would leave literal \n characters).
45+
run: |
46+
cat > /tmp/lock-header <<'EOF'
47+
# Pinned lock file — generated by pip-compile (pip-tools).
48+
# Install: pip install -r requirements-lock.txt
49+
# Update: pip-compile requirements.txt --output-file requirements-lock.txt --no-header --annotation-style=line --allow-unsafe --upgrade
50+
# Run periodically (e.g. via the "Update dependency lock file" CI workflow) to pick up
51+
# upstream patch / security releases within the bounded ranges in requirements.txt.
52+
EOF
53+
cat /tmp/lock-header requirements-lock.txt > /tmp/lock.tmp
54+
mv /tmp/lock.tmp requirements-lock.txt
55+
56+
- name: Open PR if lock file changed
57+
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
58+
with:
59+
commit-message: "chore: update requirements-lock.txt"
60+
branch: "chore/update-lock-file"
61+
delete-branch: true
62+
title: "chore: update dependency lock file"
63+
body: |
64+
Automated weekly refresh of `requirements-lock.txt`.
65+
66+
Generated by `pip-compile --upgrade` from the bounded specifiers
67+
in `requirements.txt` (must match `pyproject.toml` `[project.dependencies]`).
68+
69+
**Dependabot pip PRs** may bump bounds in `requirements.txt` / `pyproject.toml`
70+
but do not regenerate this lock file — merge those first, then merge this PR
71+
(or run **Actions → Update dependency lock file → Run workflow**).
72+
73+
Review the diff to confirm no unexpected major-version jumps before merging.
74+
labels: dependencies

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ source venv/bin/activate
6161
pip install -r requirements.txt
6262
```
6363

64+
For reproducible installs (same versions as CI), use the pinned lock file:
65+
66+
```bash
67+
pip install -r requirements-lock.txt
68+
```
69+
70+
### Dependency bounds and lock file
71+
72+
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.
73+
74+
**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.
75+
76+
Regenerate after editing bounds (prefer **Actions → Update dependency lock file → Run workflow**, or on Linux / WSL):
77+
78+
```bash
79+
pip install pip-tools
80+
pip-compile requirements.txt \
81+
--output-file requirements-lock.txt \
82+
--no-header \
83+
--annotation-style=line \
84+
--allow-unsafe
85+
```
86+
87+
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`.
88+
89+
**Automated updates:**
90+
91+
- **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.
92+
- **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`.
93+
6494
## Quick Start (Web UI)
6595

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

74104
## Tests
75105

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

78108
```bash
79109
python -m unittest discover tests -v
@@ -147,7 +177,9 @@ Cursor CLI agent sessions are read from `~/.cursor/chats/` (the default path use
147177
```
148178
cursor-chat-browser-python/
149179
├── app.py # Flask application entry point
150-
├── requirements.txt # Python dependencies
180+
├── requirements.txt # Runtime bounds (mirrors pyproject.toml)
181+
├── requirements-lock.txt # Pinned lock file used by CI
182+
├── pyproject.toml # Package metadata and canonical dependency bounds
151183
├── api/ # API route blueprints
152184
│ ├── workspaces.py # /api/workspaces endpoints
153185
│ ├── composers.py # /api/composers endpoints

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ requires-python = ">=3.10"
1818
dependencies = [
1919
"flask>=3.0,<4",
2020
"fpdf2>=2.7,<3",
21-
# Security floor: fpdf2 allows Pillow>=8.3.2, so 9.x can still be resolved.
22-
# CVE-2024-28219 (buffer overflow) fixed in Pillow 10.3.0 — https://nvd.nist.gov/vuln/detail/CVE-2024-28219
23-
"pillow>=10.3.0",
21+
# Security floor: fpdf2 allows Pillow>=8.3.2 (no upper cap); pin 12.x to avoid
22+
# known high-severity CVEs in Pillow 10.x (e.g. CVE-2024-28219 and later advisories).
23+
"pillow>=12.2.0,<13",
2424
]
2525

2626
[project.optional-dependencies]

requirements-lock.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Pinned lock file — generated by pip-compile (pip-tools).
2+
# Install: pip install -r requirements-lock.txt
3+
# Update: pip-compile requirements.txt --output-file requirements-lock.txt --no-header --annotation-style=line --allow-unsafe --upgrade
4+
# Run periodically (e.g. via the "Update dependency lock file" CI workflow) to pick up
5+
# upstream patch / security releases within the bounded ranges in requirements.txt.
6+
# Lock is generated on Linux (CI / update-lock.yml). Windows-only transitives (e.g.
7+
# colorama via click) are omitted — pip still installs them on Windows when needed.
8+
blinker==1.9.0 # via flask
9+
click==8.4.0 # via flask
10+
defusedxml==0.7.1 # via fpdf2
11+
flask==3.1.3 # via -r requirements.txt
12+
fonttools==4.63.0 # via fpdf2
13+
fpdf2==2.8.7 # via -r requirements.txt
14+
itsdangerous==2.2.0 # via flask
15+
jinja2==3.1.6 # via flask
16+
markupsafe==3.0.3 # via flask, jinja2, werkzeug
17+
pillow==12.2.0 # via -r requirements.txt, fpdf2
18+
werkzeug==3.1.8 # via flask

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
# pip install -e ".[desktop]" (+ pywebview for the GUI launcher)
77
flask>=3.0,<4
88
fpdf2>=2.7,<3
9-
pillow>=10.3.0
9+
pillow>=12.2.0,<13
1010
# pywebview is desktop-only — install with: pip install -e ".[desktop]"

0 commit comments

Comments
 (0)