Skip to content
Open
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
29 changes: 29 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# but the CI gate (full backend suite + web build) decides whether they merge.
version: 2
updates:
# Python backend (single hashed requirements.lock covers api + the data-service subset)
- package-ecosystem: pip
directory: /services/api
schedule: { interval: weekly }
Expand All @@ -10,18 +11,46 @@ updates:
directory: /services/data
schedule: { interval: weekly }
open-pull-requests-limit: 5
# npm workspace — the only package-lock.json is at the repo root
- package-ecosystem: npm
directory: / # npm workspace — the only package-lock.json is at the repo root
schedule: { interval: weekly }
open-pull-requests-limit: 5
groups:
thatopen: # keep the pinned viewer pair moving together
patterns: ["@thatopen/*", "three", "web-ifc"]
vite: # keep the Vite 8 + Vitest 4 build/test stack moving together
patterns: ["vite", "vitest", "@vitejs/*", "rolldown", "@rolldown/*"]
# Tauri / Rust desktop app
- package-ecosystem: cargo
directory: /apps/web/src-tauri
schedule: { interval: weekly }
open-pull-requests-limit: 3
# CI workflow actions
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }
open-pull-requests-limit: 3
# Container images pinned in the compose files (postgres, minio, nginx, caddy, redis). Without this,
# pinned tags silently drift — the exact gap the manual container PRs (MinIO/nginx pins, Postgres 16->17) closed.
- package-ecosystem: docker-compose
directory: /
schedule: { interval: weekly }
open-pull-requests-limit: 5
groups:
infra: # one PR for the dev/prod infra set, not five
patterns: ["postgres", "minio/minio", "nginx", "caddy", "redis"]
# Dockerfile FROM base images (node:22-slim, python:3.12-slim, nginx:1.31-alpine) — one entry per
# directory that holds a Dockerfile, since Dependabot scopes docker updates per directory.
- package-ecosystem: docker
directory: /services/converter
schedule: { interval: weekly }
open-pull-requests-limit: 3
- package-ecosystem: docker
directory: /services/api
schedule: { interval: weekly }
open-pull-requests-limit: 3
- package-ecosystem: docker
directory: /apps/web
schedule: { interval: weekly }
open-pull-requests-limit: 3
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

- uses: actions/setup-node@v5
with:
node-version: "20"
node-version: "22"
cache: npm
cache-dependency-path: package-lock.json

Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:

- uses: actions/setup-node@v5
with:
node-version: "20"
node-version: "22"
cache: npm
cache-dependency-path: package-lock.json

Expand All @@ -110,6 +110,16 @@ jobs:
with:
workspaces: apps/web/src-tauri -> target

# A stale/missing Cargo.lock has broken tagged desktop releases before (cargo silently
# re-resolved transitive deps). Fail the release build loudly if the committed lock is out
# of sync with Cargo.toml instead of letting it drift at build time.
- name: Verify Cargo.lock is committed and consistent (release build honors the committed lock)
working-directory: apps/web/src-tauri
run: |
git ls-files --error-unmatch Cargo.lock >/dev/null || { echo "Cargo.lock is not committed — run the 'Generate Cargo.lock' workflow"; exit 1; }
# --locked validates the committed lock against Cargo.toml WITHOUT upgrading transitive patches
cargo metadata --locked --format-version 1 >/dev/null || { echo "Cargo.lock out of sync — run 'cargo generate-lockfile' in apps/web/src-tauri and commit before releasing"; exit 1; }

# npm workspace: install at the root so the web build (beforeBuildCommand) has its deps
- name: Install web dependencies
run: npm ci
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

- uses: actions/setup-node@v5
with:
node-version: "20"
node-version: "22"
cache: npm
cache-dependency-path: package-lock.json

Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,14 @@ jobs:
chmod +x "binaries/aec-bim-server-${triple}"
echo "stubbed binaries/aec-bim-server-${triple}"

- name: Verify Cargo.lock is committed and consistent
run: |
git ls-files --error-unmatch Cargo.lock >/dev/null || { echo "Cargo.lock is not committed — run the 'Generate Cargo.lock' workflow"; exit 1; }
# --locked validates the committed lock against Cargo.toml WITHOUT upgrading transitive patches
cargo metadata --locked --format-version 1 >/dev/null || { echo "Cargo.lock is out of sync with Cargo.toml — run 'cargo generate-lockfile' here and commit"; exit 1; }

- name: Format check
run: cargo fmt --all -- --check

- name: Clippy (deny warnings)
run: cargo clippy --all-targets -- -D warnings
run: cargo clippy --locked --all-targets -- -D warnings
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with: { node-version: "20", cache: npm, cache-dependency-path: package-lock.json }
with: { node-version: "22", cache: npm, cache-dependency-path: package-lock.json }
- run: npm ci
- name: npm audit (production deps)
continue-on-error: true # report-only
Expand Down
4 changes: 2 additions & 2 deletions apps/web/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Build the Vite app, then serve the static bundle with nginx.
# Build context = repo root (this is an npm workspace: the only lockfile is the root package-lock.json).
FROM node:20-slim AS build
FROM node:22-slim AS build
WORKDIR /repo
# Manifests + lockfile first, so `npm ci` is a reproducible, cache-friendly layer that only re-runs when
# deps change — not on every source edit. `npm ci` (vs `npm install`) installs EXACTLY the locked tree
Expand All @@ -11,7 +11,7 @@ RUN npm ci
COPY apps/web/ apps/web/
RUN npm run build --workspace apps/web # prebuild copies web-ifc WASM into public/wasm/

FROM nginx:alpine
FROM nginx:1.31-alpine
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /repo/apps/web/dist /usr/share/nginx/html
EXPOSE 80
22 changes: 11 additions & 11 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
"node": ">=20"
"node": ">=22"
},
"scripts": {
"predev": "node scripts/copy-wasm.mjs",
Expand Down Expand Up @@ -36,33 +36,33 @@
"@tauri-apps/plugin-fs": "^2.5.1",
"@thatopen/components": "3.4.6",
"@thatopen/components-front": "3.4.3",
"@thatopen/fragments": "3.4.5",
"@thatopen/ui": "3.4.3",
"@thatopen/fragments": "3.4.6",
"@thatopen/ui": "3.4.10",
"camera-controls": "3.1.2",
"geotiff": "^3.0.5",
"laz-perf": "^0.0.7",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "^6.0.227",
"qrcode": "^1.5.4",
"three": "0.184.0",
"three": "0.185.1",
"web-ifc": "0.0.77"
},
"devDependencies": {
"@capacitor/android": "^7",
"@capacitor/cli": "^7",
"@capacitor/core": "^7",
"@capacitor/ios": "^7",
"@capacitor/android": "^8",
"@capacitor/cli": "^8",
"@capacitor/core": "^8",
"@capacitor/ios": "^8",
"@eslint/js": "^9.39.5",
"@types/qrcode": "^1.5.6",
"@types/three": "0.184.1",
"@types/three": "0.185.1",
"eslint": "^9.39.5",
"globals": "^17.7.0",
"happy-dom": "^20.8.9",
"openapi-typescript": "^7.13.0",
"typescript": "5.9.3",
"typescript-eslint": "^8.63.0",
"vite": "6.4.3",
"vite": "8.1.5",
"vite-plugin-pwa": "^1.3.0",
"vitest": "^3.2.6"
"vitest": "^4.1.10"
}
}
3 changes: 2 additions & 1 deletion apps/web/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ serde_json = "1"
# Pin transitive `time` away from 0.3.52: that release changed the (sealed) Parsable trait's
# parse signature, which `cookie` 0.18.1 (pulled in via tauri-plugin-updater -> reqwest) still
# calls the old way, breaking the desktop build. 0.3.51 is the last version that compiles with it.
# (We have no committed Cargo.lock, so CI resolves deps fresh each run and picked up the break.)
# Cargo.lock IS committed and CI runs cargo with --locked (rust-ci.yml / desktop.yml verify the
# lock is in sync), so this pin is now a belt-and-suspenders guard against an accidental `cargo update`.
time = "=0.3.51"
4 changes: 2 additions & 2 deletions apps/web/src/viewer/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ export function initViewerApp(ctx: ViewerCtx): ViewerApp {
if (!projectId) { notify("connect a project first", "error"); return; }
const r = viewer.world.renderer!.three;
r.render(viewer.world.scene.three, viewer.world.camera.three);
r.domElement.toBlob(async (blob) => {
r.domElement.toBlob(async (blob: Blob | null) => {
if (!blob) { notify("couldn't capture the canvas", "error"); return; }
b.disabled = true;
try {
Expand Down Expand Up @@ -810,7 +810,7 @@ export function initViewerApp(ctx: ViewerCtx): ViewerApp {
let fitPending = false; // set when a fit was skipped because the viewport was hidden (0×0)
async function fitToModels() {
const box = new THREE.Box3();
viewer.world.scene.three.traverse((o) => {
viewer.world.scene.three.traverse((o: THREE.Object3D) => {
const m = o as THREE.Mesh & THREE.Points;
if (m.isMesh || m.isPoints) box.expandByObject(o); // include reference point clouds, not just meshes
});
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/viewer/envTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export function installEnvTools(d: EnvToolsDeps): { isRenderOn: () => boolean }
let storeys: { name: string | null; elevation: number; guid: string }[] = [];
try { storeys = await d.api.drawingStoreys(pid); } catch { d.notify("no storeys (needs source IFC)", "error"); return; }
const box = new THREE.Box3();
d.viewer.world.scene.three.traverse((o) => { const msh = o as THREE.Mesh; if (msh.isMesh) box.expandByObject(msh); });
d.viewer.world.scene.three.traverse((o: THREE.Object3D) => { const msh = o as THREE.Mesh; if (msh.isMesh) box.expandByObject(msh); });
const size = box.isEmpty() ? 20 : Math.max(box.getSize(new THREE.Vector3()).x, box.getSize(new THREE.Vector3()).z) * 1.1;
const cx = box.isEmpty() ? 0 : box.getCenter(new THREE.Vector3()).x;
const cz = box.isEmpty() ? 0 : box.getCenter(new THREE.Vector3()).z;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/viewer/measureSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function installSectionBox(d: Pick<MeasureSectionDeps, "viewer" | "loader
const r = d.viewer.world.renderer!.three;
if (sectionBox) { r.clippingPlanes = []; sectionBox = null; b.classList.remove("on"); void d.loader.fragments.core.update(true); return; }
const box = new THREE.Box3();
d.viewer.world.scene.three.traverse((o) => { const msh = o as THREE.Mesh; if (msh.isMesh) box.expandByObject(msh); });
d.viewer.world.scene.three.traverse((o: THREE.Object3D) => { const msh = o as THREE.Mesh; if (msh.isMesh) box.expandByObject(msh); });
if (box.isEmpty()) { d.notify("no model to clip", "error"); return; }
const c = box.getCenter(new THREE.Vector3());
const s = box.getSize(new THREE.Vector3()).multiplyScalar(0.35); // keep the middle ~70%
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/viewer/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export function renderMode(world: World, on: boolean): void {
}

// (Re)apply cast/receive flags + the PBR material swap to all current model meshes.
s.traverse((o) => {
s.traverse((o: THREE.Object3D) => {
const m = o as THREE.Mesh;
if (m.isMesh && m.name !== GROUND) {
m.castShadow = on;
Expand Down
20 changes: 15 additions & 5 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,22 @@ return {
},
build: {
chunkSizeWarningLimit: 4000,
rollupOptions: {
// Vite 8: build.rollupOptions was renamed to build.rolldownOptions (Rolldown bundler).
rolldownOptions: {
output: {
// split heavy vendor libs into cacheable chunks (they change far less than app code)
manualChunks: {
three: ["three"],
thatopen: ["@thatopen/components", "@thatopen/components-front", "@thatopen/fragments"],
// split heavy vendor libs into cacheable chunks (they change far less than app code).
// NOTE: the object form of `manualChunks` was REMOVED in Vite 8; the function form still
// works (deprecated). Migrate to Rolldown's native `advancedChunks` as a follow-up:
// build.rolldownOptions.output.advancedChunks = { groups: [{ name, test }] }
manualChunks(id) {
if (id.includes("node_modules/three")) return "three";
if (
id.includes("@thatopen/components") ||
id.includes("@thatopen/components-front") ||
id.includes("@thatopen/fragments")
) {
return "thatopen";
}
},
},
},
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Convert a model (job): docker compose run --rm converter /data/model.ifc /data/model.frag
services:
postgres:
image: postgres:16
image: postgres:17
environment:
POSTGRES_USER: "${POSTGRES_USER:-bim}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-bim}"
Expand All @@ -16,7 +16,7 @@ services:
retries: 10

minio:
image: minio/minio
image: minio/minio:RELEASE.2025-10-15T17-29-55Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: "${S3_ACCESS_KEY:-minioadmin}"
Expand Down
Loading