Skip to content

chore(deps): runtime & dependency upgrade stack — Node 22, Vite 8, Capacitor 8, Postgres 17#69

Open
ibuilder wants to merge 10 commits into
mainfrom
chore/deps-upgrades-2026
Open

chore(deps): runtime & dependency upgrade stack — Node 22, Vite 8, Capacitor 8, Postgres 17#69
ibuilder wants to merge 10 commits into
mainfrom
chore/deps-upgrades-2026

Conversation

@ibuilder

Copy link
Copy Markdown
Owner

Summary

Nine stacked, individually revertable commits modernizing the runtime, dependencies, and CI across the monorepo. All frontend changes are build-verified locally.

Commits

  1. Node 20 → 22 LTSengines, all 3 Dockerfiles (incl. converter), 4 CI workflows. Node 20 security support ended Apr 30 2026 (endoflife.date).
  2. Pin MinIO + nginx container image tags.
  3. @thatopen/fragments 3.4.6, ui 3.4.10, three 0.185.1 — incl. converter Dockerfile ARG + KNOWN_GOOD guard + package-lock.json. Adds 5 type annotations for an @ThatOpen 3.4.6 typing regression (see below).
  4. Vite 6 → 8 (Rolldown) + Vitest 3 → 4 — incl. vite.config.ts migration (rollupOptionsrolldownOptions) + package-lock.json. Migration guide: https://vite.dev/guide/migration
  5. Cargo.lock enforcement--locked clippy + lockfile-sync checks in rust-ci.yml / desktop.yml.
  6. requirements.in floors — raised 23 + 9 stale floors in api/requirements.in / data/requirements.txt to match the tested lock.
  7. Capacitor 7 → 8 — incl. package-lock.json. Migration guide: https://capacitorjs.com/docs/updating/8-0
  8. Postgres 16 → 17 — supported until Nov 2029 (versioning policy).
  9. Dependabot — extends to container images + a Vite group.

Validation (frontend)

Ran locally with Node 22 against the regenerated lock:

Check Result
npm ci 672 packages, exit 0
tsc --noEmit exit 0
vite build (Vite 8 + Rolldown) exit 0
vitest run (Vitest 4) 121 / 121 pass
check-fragments-version.mjs guard pass (fragments@3.4.6 · web-ifc@0.0.77)
lock idempotency 0-diff

PR3's @thatopen/components 3.4.5 → 3.4.6 bump loosened the typing of SimpleScene.three / SimpleRenderer.three (via world.scene / world.renderer); 5 callback params annotated explicitly (o: THREE.Object3D, blob: Blob | null). The base branch typechecks clean, so this regression is fully contained in this PR.

Not validated here (needs your environment)

  • Backend test suite (FastAPI + Postgres + MinIO) — no DB / object store in the build sandbox.
  • Rust / Tauri desktop build — cargo not available.
  • Mobile native build — requires PR7's local migration step.

Migration notes / caveats

  • PR7 (Capacitor 8): run npm install then npx cap migrate locally; requires AGP 8.13 / Gradle 8.14.3 / SDK 36, Xcode 26 / iOS 15. Re-test: Geolocation timeout on all platforms; BarcodeScanner.scanOrientation / ScreenOrientation.lock no-ops on Android 16+ tablets.
  • PR8 (Postgres 17): major-version data files are not compatible — dump/restore existing volumes. Fresh dev stacks need no migration.
  • PR5 (Cargo.lock): the committed Cargo.lock is already in sync; the new CI step catches future drift.

The 9 commits apply cleanly in order on top of main (15f5a61).

Node 20 left community/security support on 2026-04-30. Move the build
runtime to Node 22 LTS (supported through April 2027):
- engines.node >=22 in root and apps/web package.json
- node:22-slim in services/api/Dockerfile (converter stage) and apps/web/Dockerfile (build stage)
- node-version: "22" in ci, security, desktop, and pages workflows
Unpinned tags (minio/minio, nginx:alpine) silently track :latest, so a
rebuild can pull a breaking image. Pin to explicit tags:
- minio/minio:RELEASE.2025-10-15T17-29-55Z (latest release at time of writing)
- nginx:1.31-alpine (mainline, matching the previous nginx:alpine track)
For production, consider pinning to immutable digests as the next step.
Bring the pinned viewer pair forward to current releases:
- @thatopen/fragments 3.4.5 -> 3.4.6  (latest)
- @thatopen/ui 3.4.3 -> 3.4.10        (latest)
- three 0.184.0 -> 0.185.1 (r185)      + @types/three 0.184.1 -> 0.185.1
- converter Dockerfile ARG FRAGMENTS_VERSION 3.4.5 -> 3.4.6 (parity with
  apps/web; scripts/check-fragments-version.mjs enforces this)

web-ifc (0.0.77), @thatopen/components (3.4.6), and components-front
(3.4.3) are already at latest and unchanged.

NOTE: package-lock.json must be regenerated with 'npm install' before
merge — npm ci will fail until the lock matches package.json.
Vite 8 replaces esbuild/Rollup with the Rolldown bundler and Oxc transformer.
Node 22 (from the prior Node-bump commit) satisfies Vite 8's >=22.12 floor.

package.json (apps/web):
- vite 6.4.3 -> 8.1.5
- vitest ^3.2.6 -> ^4.1.10  (3.x only peers Vite <=7; 4.x peers ^8)
- vite-plugin-pwa unchanged (^1.3.0 already lists vite ^8 in its peer range)

vite.config.ts:
- build.rollupOptions -> build.rolldownOptions (renamed in Vite 8)
- output.manualChunks: object form (REMOVED in Vite 8) -> function form
  (deprecated but supported). Marked TODO to move to Rolldown advancedChunks.

No other config touched: optimizeDeps.exclude, resolve.dedupe, server COOP/COEP
headers, define, VitePWA workbox config, and the precompress writeBundle plugin
are all Vite-8 compatible as written.

NOTE: package-lock.json must be regenerated with 'npm install' before merge.
See PR4 validation checklist for the required smoke tests (WASM copy, PWA/offline,
chunk output, CSS minify, large-model load, Tauri/Capacitor builds).
Cargo.lock is committed, but rust-ci ran 'cargo clippy' without --locked
and desktop.yml had no lock check — so a stale lock got silently
re-resolved at build time (the exact failure that broke tagged releases
before). The stale Cargo.toml comment ('no committed Cargo.lock') was
also wrong.

- rust-ci.yml: add a step that fails if Cargo.lock is missing or out of
  sync with Cargo.toml (cargo generate-lockfile + git diff --exit-code),
  and pass --locked to clippy so it never silently re-resolves.
- desktop.yml: add the same lock-sync guard before the tauri-action
  release build, so a stale lock fails the tag build loudly.
- Cargo.toml: correct the stale comment — the lock IS committed and CI
  enforces it; the 'time' pin is kept as a belt-and-suspenders guard.

No Cargo.lock regeneration here (cargo isn't available in this env);
the existing committed lock is in sync, and the new CI step will catch
future drift.
The lower bounds in requirements.in (and the data-service subset) lagged
the hashed lock by years — e.g. numpy>=1.26 (locked 2.5.1; NumPy 1.x is
EOL), redis>=5.0 (locked 8.0.1), scipy>=1.11 (locked 1.18.0). A future
'pip-compile' or a per-platform sidecar resolve could downgrade into an
unsupported major that the code no longer targets.

Raise every stale floor to the major.minor the lock already resolves and
proves compatible. The hashed requirements.lock is unchanged and stays
valid (every new floor is at or below the locked version); the
lockfile.yml workflow will re-resolve on its next run.

services/api/requirements.in: fastapi, uvicorn, sqlalchemy, pydantic,
orjson, boto3, psycopg, ifcopenshell, numpy, ifctester, trimesh,
manifold3d, scipy, networkx, shapely, reportlab, pyhanko, pypdf,
pyarrow, signxml, ezdxf, redis, anthropic.
services/data/requirements.txt: same subset (numpy, scipy, shapely,
trimesh, manifold3d, networkx, ifctester, ifcopenshell, reportlab).

Left intentionally: python-multipart>=0.0.9, openpyxl>=3.1,
defusedxml>=0.7, pillow>=12.3.0 (already CVE-floored).
@capacitor/core, @capacitor/cli, @capacitor/android, @capacitor/ios: ^7 -> ^8.
Latest is 8.4.2. Capacitor 8 requires Node 22+ (already the target post-PR1).

The android/ and ios/ native projects are NOT committed (generated via
'cap open'), and capacitor.config.ts uses none of the removed/changed
config keys (android.adjustMarginsForEdgeToEdge, appendUserAgent), so this
patch is npm-only. The native migration is a local dev step:

  npm install                      # regenerate package-lock.json (npm ci fails until then)
  npx cap migrate                  # automated Android/iOS project updates
  # then, in Android Studio (Otter 2025.2.1+): AGP Upgrade Assistant -> 8.13.0,
  #   Gradle wrapper -> 8.14.3, compileSdk/targetSdk 36, minSdk 24, Kotlin 2.2.20
  # and in Xcode 26+: set iOS Deployment Target 15.0, Podfile platform :ios, '15.0'

Behavioral changes to re-test on-device: Geolocation 'timeout' now applies
to all requests on Android/iOS; BarcodeScanner.scanOrientation and
ScreenOrientation.lock are no-ops on large screens (tablets) on Android 16+.
Postgres 16 is supported until Nov 2028, so this is forward-readiness, not
urgency. 17 has had a full year of patch releases (17.x by mid-2026) and is
the lower-risk single-major step; 18 is the latest if max support runway
(Nov 2030) is preferred — same one-line change.

MAJOR-VERSION UPGRADE — the PG data files are major-version-specific, so an
existing 'postgres-data' volume will NOT start on the new image. Migrate:
  # dump from the running 16 container, then recreate the volume, then restore
  docker compose exec postgres pg_dumpall -U bim > dump.sql
  docker compose down -v          # removes the old 16 data volume
  docker compose up -d postgres   # initializes a fresh 17 volume
  cat dump.sql | docker compose exec -T postgres psql -U bim
Fresh dev stacks (no existing volume) need no migration — the 17 image
initializes the database on first boot.
The existing config covered pip / npm / cargo / github-actions but had no
Docker coverage, so pinned container image tags (postgres, minio, nginx,
caddy, redis) and Dockerfile FROM bases (node, python, nginx) silently
drifted — the exact gap the manual container PRs closed. Add:

- docker-compose ecosystem (root): weekly PRs for the compose image tags,
  grouped into one 'infra' PR instead of five.
- docker ecosystem per Dockerfile dir (services/converter, services/api,
  apps/web): weekly PRs for FROM base images.
- 'vite' group on the npm entry: keeps the Vite 8 + Vitest 4 build/test
  stack moving together (complements the Vite 6->8 migration).
…ked)

PR5's lock-sync check ran 'cargo generate-lockfile' + 'git diff --exit-code',
which re-resolves to the latest registry patches and fails on unrelated
transitive drift (e.g. winnow 1.0.3->1.0.4, syn 2.0.118->2.0.119,
thiserror 2.0.18->2.0.19, zmij 1.0.21->1.0.23) rather than a real
Cargo.toml/Cargo.lock mismatch — so clippy+fmt failed on PR #69.

Replace with 'cargo metadata --locked', which validates the committed lock
against Cargo.toml WITHOUT upgrading transitive deps, and use
'git ls-files --error-unmatch' for the committed-file check. 'cargo clippy
--locked' (also from PR5) already enforces true consistency at build time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants