diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2675e6f2..be3e135ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,6 +174,28 @@ aggregate instead: an italic *Catalog* line at the end of the version section an idle window — so the instance is in practice never reclaimed and visitors keep the same time to first byte. `anyplot-api` keeps `min-instances=1`: its cold start is ~11.6 s and its traffic does leave gaps over 15 minutes. (#10812) +- **The frontend declares the Node version it is actually built with, and something + enforces it** — `app/package.json` asked for `node >=20` while the image that produces + the deployed bundle builds on Node 22 and CI tests on Node 24, so the only version the + manifest still admitted was the one nothing tests and that reached end of life in April + 2026. The floor moves to `>=22.12.0` — the version the build path actually requires + (Vite and rolldown declare `^20.19.0 || >=22.12.0`, so `>=22` would have advertised + 22.0–22.11 as supported and let Vite's own engine check reject them instead) — `app/.nvmrc` + names 22 for `nvm use` and `setup-node`, and `app/.npmrc` sets `engine-strict=true` so an + npm install in `app/` refuses an unsupported runtime at install time rather than failing + later inside the build with a message that never mentions the version (yarn 1, the app's + package manager, checks `engines` itself). `docs/development.md` said "Node.js 20+" and now + matches. Same pin as the sibling repo kurrentschrift. (#11206) + +### Security + +- **`click` 8.3.1 → 8.3.3 closes PYSEC-2026-2132** — the only advisory `pip-audit` + reports against the resolved runtime dependency set (`uv export --no-dev`), which now + comes back clean. A transitive dependency, so the fix is a lock-file bump with no + `pyproject.toml` constraint, per the repository's dependency rule. The bump is the + minimal one that clears the advisory; `click` 8.5.0 exists and is left to Dependabot, + where a minor bump of the library behind every console script gets its own PR and its + own CI run. (#11206) ## [3.2.0] — 2026-08-29 — Findable by assistants diff --git a/app/.npmrc b/app/.npmrc new file mode 100644 index 0000000000..e6bcb3d263 --- /dev/null +++ b/app/.npmrc @@ -0,0 +1,23 @@ +# package.json declares `engines.node` and, until this file existed, nothing +# enforced it: npm ignores the field unless engine-strict is set, so a machine +# on an older Node installed happily and only failed later inside the build, +# with an error that never mentions the version. Enforced here, the refusal +# happens at install time and names the version it wants. +# +# The app's package manager is yarn (CLAUDE.md "Package Management"), and +# yarn 1 checks `engines` on its own. This file covers the other half: anyone +# who reaches for npm in `app/` — a quick `npm ci`, a tool that shells out to +# npm — gets the same refusal instead of a silently unsupported toolchain. +# +# The declared floor is 22.12 because that is what the build path needs (Vite +# and rolldown: `^20.19.0 || >=22.12.0`). `.nvmrc` says plain `22` — the latest +# 22.x — on purpose, because the dev-only test stack reaches higher still +# (jsdom wants `^22.22.2`); a floor tracking a transitive dev dependency's +# patch version would need editing on every bump. +engine-strict=true + +# The audit step of a local install reports the registry's view of dev +# dependencies, which the deployed bundle does not contain, and costs minutes +# for an answer no one acts on here. Dependabot watches the manifest instead. +audit=false +fund=false diff --git a/app/.nvmrc b/app/.nvmrc new file mode 100644 index 0000000000..2bd5a0a98a --- /dev/null +++ b/app/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/app/package.json b/app/package.json index 2de95e48a8..af915c8c98 100644 --- a/app/package.json +++ b/app/package.json @@ -18,7 +18,7 @@ "test:watch": "vitest" }, "engines": { - "node": ">=20" + "node": ">=22.12.0" }, "packageManager": "yarn@1.22.22", "dependencies": { diff --git a/docs/development.md b/docs/development.md index 2927b64c93..70825e3254 100644 --- a/docs/development.md +++ b/docs/development.md @@ -7,7 +7,9 @@ Guide for setting up a local development environment. ## Prerequisites - **Python 3.13+** -- **Node.js 20+** and yarn +- **Node.js 22.12+** and yarn — `app/.nvmrc` names the version to use (`nvm use` in `app/`), + and `app/package.json` refuses anything older. Take the latest 22.x: the build toolchain + needs 22.12, and the dev-only test stack (jsdom) currently wants 22.22. - **PostgreSQL** (or access to Cloud SQL) - **uv** - Fast Python package manager diff --git a/uv.lock b/uv.lock index 4e3f791dc0..3e2737f835 100644 --- a/uv.lock +++ b/uv.lock @@ -854,14 +854,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.1" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -1564,8 +1564,8 @@ name = "httpcore2" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11" }, - { name = "truststore" }, + { name = "h11", marker = "sys_platform != 'emscripten'" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } wheels = [ @@ -2747,12 +2747,12 @@ resolution-markers = [ "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "matplotlib" }, - { name = "mizani" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "scipy" }, - { name = "statsmodels" }, + { name = "matplotlib", marker = "sys_platform != 'emscripten'" }, + { name = "mizani", marker = "sys_platform != 'emscripten'" }, + { name = "numpy", marker = "sys_platform != 'emscripten'" }, + { name = "pandas", marker = "sys_platform != 'emscripten'" }, + { name = "scipy", marker = "sys_platform != 'emscripten'" }, + { name = "statsmodels", marker = "sys_platform != 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/f4/c06a16b4ec540ff6af22e32144704f7e86dee18cd56eef14d66f9615cbf2/plotnine-0.15.7.tar.gz", hash = "sha256:6e578bdd93c7ba12ce495dcdeaf8651f8e4ccdceb0c85b94c8195566d709c2a0", size = 6855478, upload-time = "2026-06-13T10:47:14.277Z" } wheels = [ @@ -2768,12 +2768,12 @@ resolution-markers = [ "python_full_version == '3.14.*' and sys_platform == 'emscripten'", ] dependencies = [ - { name = "matplotlib" }, - { name = "mizani" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "scipy" }, - { name = "statsmodels" }, + { name = "matplotlib", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, + { name = "mizani", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, + { name = "pandas", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, + { name = "scipy", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, + { name = "statsmodels", marker = "python_full_version >= '3.14' and sys_platform == 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/5d/3471689cf15cf2b6ebb6ebdebc7e33831fb35d85e2323875b5a0e150505d/plotnine-0.15.8.tar.gz", hash = "sha256:d3859997c3abd6edff6dee376d912716e75bbe0b0099b7054176d84fe11e8e20", size = 6863148, upload-time = "2026-08-14T16:27:49.399Z" } wheels = [ @@ -2788,12 +2788,12 @@ resolution-markers = [ "python_full_version < '3.14' and sys_platform == 'emscripten'", ] dependencies = [ - { name = "matplotlib" }, - { name = "mizani" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "scipy" }, - { name = "statsmodels" }, + { name = "matplotlib", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, + { name = "mizani", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, + { name = "numpy", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, + { name = "pandas", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, + { name = "scipy", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, + { name = "statsmodels", marker = "python_full_version < '3.14' and sys_platform == 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/e8/d28be1fee4b1c7ef05f76bfb5a137ebc9b214f80b06b2771c73903d421e0/plotnine-0.16.0a12.tar.gz", hash = "sha256:e95565d9ae16ed60bfa16bcea2cb3c862e4b3165b8c257b8a96498f39bb8b17d", size = 7464639, upload-time = "2026-08-15T12:05:51.042Z" } wheels = [ @@ -3605,8 +3605,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, + { name = "cryptography", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "jeepney", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [