Skip to content

Commit 21810da

Browse files
committed
Merge branch 'main' into harden/sidecar-eval-and-job-limits
Resolve the test_whitebox_endpoints.py conflict by keeping both sides: main's COG-conversion tests for _ensure_raster_outputs_are_cogs and this branch's in-flight job cap / oversized layer input tests.
2 parents 4e39697 + e910e01 commit 21810da

105 files changed

Lines changed: 11084 additions & 431 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/mas-store.yml

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: Build Mac App Store package
2+
3+
# Manually builds the sandboxed Mac App Store variant and uploads the signed
4+
# .pkg as a workflow artifact for submission via Transporter or
5+
# `xcrun altool --upload-app`. Like msix-store.yml, this never touches a GitHub
6+
# release; it exists so a Store-ready package can be produced on demand.
7+
#
8+
# The MAS variant differs from the Developer ID builds in release.yml:
9+
# - `mas` cargo feature: the Python sidecar, Jupyter, martin, and external
10+
# plugin installation are compiled out (App Sandbox forbids spawning
11+
# downloaded executables; guideline 2.5.2 forbids downloading executable
12+
# code). Client-side engines (DuckDB-WASM, Whitebox WASM, Turf, Pyodide)
13+
# keep working because WebKit executes them.
14+
# - App Sandbox entitlements + embedded provisioning profile
15+
# (tauri.mas.conf.json), signed with "Apple Distribution" instead of
16+
# "Developer ID Application"; no notarization (not applicable to MAS).
17+
# - GEOLIBRE_STORE_BUILD=1 strips the in-app update flow (set by
18+
# scripts/tauri-build.mjs --mas), matching Apple guideline 2.4.5(vii).
19+
#
20+
# Required secrets (see docs/mac-app-store.md for how to create them):
21+
# APPLE_MAS_CERTIFICATE base64 .p12 holding BOTH the
22+
# "Apple Distribution" and the
23+
# "3rd Party Mac Developer Installer"
24+
# certificates with their private keys
25+
# APPLE_MAS_CERTIFICATE_PASSWORD password of that .p12
26+
# APPLE_MAS_SIGNING_IDENTITY e.g. "Apple Distribution: Name (TEAMID)"
27+
# APPLE_MAS_INSTALLER_IDENTITY e.g. "3rd Party Mac Developer Installer: Name (TEAMID)"
28+
# APPLE_MAS_PROVISIONING_PROFILE base64 Mac App Store .provisionprofile
29+
# for org.geolibre.desktop
30+
# APPLE_TEAM_ID already configured for release.yml
31+
32+
on:
33+
workflow_dispatch:
34+
35+
permissions:
36+
contents: read
37+
38+
jobs:
39+
build:
40+
name: Build MAS pkg
41+
runs-on: macos-latest
42+
steps:
43+
- name: Checkout repository
44+
uses: actions/checkout@v7
45+
with:
46+
# npm ci runs third-party lifecycle scripts; keep the checkout token
47+
# out of the local git config they could read.
48+
persist-credentials: false
49+
50+
- name: Verify signing secrets are configured
51+
env:
52+
APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }}
53+
APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }}
54+
APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }}
55+
APPLE_MAS_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }}
56+
APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }}
57+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
58+
run: |
59+
missing=()
60+
for name in APPLE_MAS_CERTIFICATE APPLE_MAS_CERTIFICATE_PASSWORD \
61+
APPLE_MAS_PROVISIONING_PROFILE APPLE_MAS_SIGNING_IDENTITY \
62+
APPLE_MAS_INSTALLER_IDENTITY APPLE_TEAM_ID; do
63+
[[ -n "${!name}" ]] || missing+=("$name")
64+
done
65+
if (( ${#missing[@]} )); then
66+
echo "::error::Missing secrets for the Mac App Store build: ${missing[*]}. See docs/mac-app-store.md."
67+
exit 1
68+
fi
69+
70+
- name: Set up Node.js
71+
uses: actions/setup-node@v7
72+
with:
73+
node-version: lts/*
74+
cache: npm
75+
cache-dependency-path: package-lock.json
76+
77+
- name: Install Rust stable
78+
# Pinned to a commit SHA (not the moving `stable` branch) because this
79+
# job later holds the App Store signing certificates in its keychain.
80+
# Update deliberately when bumping the toolchain action.
81+
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
82+
83+
- name: Install universal-build Rust targets
84+
run: rustup target add aarch64-apple-darwin x86_64-apple-darwin
85+
86+
- name: Install frontend dependencies
87+
run: npm ci
88+
89+
- name: Import signing certificates into a temporary keychain
90+
env:
91+
APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }}
92+
APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }}
93+
run: |
94+
keychain="$RUNNER_TEMP/mas-signing.keychain-db"
95+
keychain_password="$(uuidgen)"
96+
security create-keychain -p "$keychain_password" "$keychain"
97+
security set-keychain-settings -lut 21600 "$keychain"
98+
security unlock-keychain -p "$keychain_password" "$keychain"
99+
printf '%s' "$APPLE_MAS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/mas.p12"
100+
security import "$RUNNER_TEMP/mas.p12" -k "$keychain" \
101+
-P "$APPLE_MAS_CERTIFICATE_PASSWORD" -f pkcs12 -A \
102+
-T /usr/bin/codesign -T /usr/bin/productbuild
103+
rm "$RUNNER_TEMP/mas.p12"
104+
security set-key-partition-list -S apple-tool:,apple:,codesign: \
105+
-s -k "$keychain_password" "$keychain" > /dev/null
106+
security list-keychains -d user -s "$keychain" login.keychain-db
107+
echo "MAS_KEYCHAIN=$keychain" >> "$GITHUB_ENV"
108+
109+
- name: Install the provisioning profile and render entitlements
110+
env:
111+
APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }}
112+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
113+
run: |
114+
printf '%s' "$APPLE_MAS_PROVISIONING_PROFILE" | base64 --decode \
115+
> apps/geolibre-desktop/src-tauri/mas/embedded.provisionprofile
116+
scripts/render-mas-entitlements.sh
117+
118+
- name: Build the sandboxed universal app
119+
env:
120+
NODE_OPTIONS: --max-old-space-size=4096
121+
VITE_GEE_OAUTH_CLIENT_ID: ${{ secrets.VITE_GEE_OAUTH_CLIENT_ID }}
122+
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }}
123+
run: npm run tauri:build:mas -- --target universal-apple-darwin
124+
125+
- name: Build the signed installer package
126+
id: build_pkg
127+
env:
128+
APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }}
129+
run: |
130+
app="apps/geolibre-desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/GeoLibre Desktop.app"
131+
[[ -d "$app" ]] || { echo "::error::No app bundle at $app"; exit 1; }
132+
# Confirm the sandbox entitlement made it into the signature before
133+
# packaging; an unsandboxed binary is an automatic App Review reject.
134+
codesign -d --entitlements - --xml "$app" | grep -q "com.apple.security.app-sandbox" \
135+
|| { echo "::error::App bundle is missing the app-sandbox entitlement"; exit 1; }
136+
version="$(node -p "require('./apps/geolibre-desktop/src-tauri/tauri.conf.json').version")"
137+
pkg="GeoLibre.Desktop_${version}_universal_mas.pkg"
138+
xcrun productbuild --sign "$APPLE_MAS_INSTALLER_IDENTITY" \
139+
--component "$app" /Applications "$pkg"
140+
echo "pkg_path=$pkg" >> "$GITHUB_OUTPUT"
141+
142+
- name: Upload MAS package artifact
143+
uses: actions/upload-artifact@v7
144+
with:
145+
name: geolibre-mas-pkg
146+
path: ${{ steps.build_pkg.outputs.pkg_path }}
147+
if-no-files-found: error
148+
149+
- name: Remove the temporary keychain
150+
if: always()
151+
run: |
152+
if [[ -n "${MAS_KEYCHAIN:-}" && -f "$MAS_KEYCHAIN" ]]; then
153+
security delete-keychain "$MAS_KEYCHAIN"
154+
fi

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,9 @@ python/examples/my-map.geolibre.json
7474
# Rendered Homebrew cask, emitted to the working dir by scripts/render-*-cask.sh
7575
# for submission to a tap / homebrew-cask; not committed to this repo.
7676
/geolibre.rb
77+
78+
# Rendered Mac App Store signing inputs (generated by
79+
# scripts/render-mas-entitlements.sh and the mas-store workflow; the committed
80+
# source of truth is Entitlements.mas.plist.template)
81+
apps/geolibre-desktop/src-tauri/mas/Entitlements.mas.plist
82+
apps/geolibre-desktop/src-tauri/mas/embedded.provisionprofile

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,6 @@ The browser build proxies the sidecar at `/sidecar` (same-origin, no CORS); conf
9595
- `MAX_VECTOR_BYTES` (`packages/plugins/src/plugins/remote-file-formats.ts`) mirrors `MAX_REMOTE_FILE_BYTES`, an **internal, unexported** constant in `maplibre-gl-vector` (2 GiB — DuckDB-WASM holds remote file sizes in 32 bits). It cannot be imported, so whenever `maplibre-gl-vector` is bumped (in `packages/plugins/package.json`) — including Dependabot PRs — re-check `src/lib/utils/remote.ts` in that package and update the mirror if it moved. If it drifts, the remote-browse panels (Source Cooperative, Hugging Face) silently block GeoParquet the engine could now open, or offer an Add that is certain to fail. Updating the constant is enough: the limit the user is shown is rendered from it, not written into the copy. `remote-file-formats.ts` is the **single** home for this and the other format/reader/size rules those panels share — a per-panel copy would miss this check, so add new browse panels against that module rather than duplicating it (`source-coop-api.ts` re-exports it under its own names for compatibility).
9696
- `MAP_PANEL_SELECTOR` (`apps/geolibre-desktop/src/components/layout/RecordVideoDialog.tsx`) mirrors the **rendered** control class names from `maplibre-gl-components``maplibre-gl-html-control`, `maplibre-gl-legend`, `maplibre-gl-colorbar` — so the Record Video "Include map panels" option can rasterize those on-map overlays into the recording. These are the display elements, deliberately **not** the `*-gui-control` authoring editors. The classes are internal and unexported, so whenever `maplibre-gl-components` is bumped (in `packages/plugins/package.json`) — including Dependabot PRs — re-check them against the rendered controls and update the selector if they moved. If a class drifts, the option silently stops burning that panel into the video (or the checkbox never appears) with no build error.
9797
- `propertySpecFor` (`packages/core/src/expressions.ts`) fabricates the **unexported** `StylePropertySpecification` shape that `@maplibre/maplibre-gl-style-spec`'s `createExpression` uses for expected-result-type enforcement (the Expression Builder's filter → boolean / color checks). The cast hides any contract change from the compiler, so whenever `@maplibre/maplibre-gl-style-spec` is bumped (including Dependabot PRs) run the frontend suite — the "enforces an expected result type" test in `tests/expressions.test.ts` fails if the shape stops being honored.
98+
- `DISTANCE_SEGMENTS` / `NON_DISTANCE_NAMES` (`apps/geolibre-desktop/src/lib/whitebox-distance-params.ts`) decide, by parameter *name*, which Whitebox parameters are ground distances and so get the Processing dialog's metric unit picker (GeoLibre#1540). The segments are generic (`tolerance`, `radius`, `length`, `resolution`), so a tool can carry a matching name that is not a length — `corridor_tolerance` is a 0-1 fraction. Those are safe today only because the picker is confined to tools whose every dataset input is a vector layer, and the colliding names happen to sit on imagery/LiDAR tools; that is a coincidence, not a guarantee. So whenever `geolibre-wasm` is bumped (in `packages/processing/package.json`) — including Dependabot PRs — scan the new catalog for a `double` matching the rule whose description reads as a fraction, ratio, angle or weight, and add it to `NON_DISTANCE_NAMES`. If one is missed, that tool's field offers metres and silently converts a dimensionless number as if it were a distance, with no build error.
9899
- UI strings are translatable via **react-i18next**; catalogs live in `apps/geolibre-desktop/src/i18n/locales/*.json` (`en.json` is the source of truth, typed by `i18next.d.ts`). Use `t()` for new user-facing strings; a `?locale`/`?lang` query param sets the embed language. The UI mirrors for right-to-left locales (Arabic), so style new components with Tailwind's logical utilities (`ms-`/`me-`/`ps-`/`pe-`/`text-start`/`border-s`/`start-`…), not the physical `ml-`/`left-` forms. See `docs/i18n.md`.
99100
- Reference docs: `docs/architecture.md`, `docs/project-format.md`, `docs/plugin-api.md`, `docs/python.md`, `docs/i18n.md`, `docs/contributing.md`.

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ ENV GEOLIBRE_CONVERSION_PYTHON=/usr/local/bin/python \
8383
GEOLIBRE_CONVERSION_ROOTS=/data
8484
RUN mkdir -p /data
8585

86+
# For the same reason, the sidecar's PostGIS endpoints refuse every destination
87+
# until GEOLIBRE_POSTGIS_HOSTS names the allowed databases (comma-separated
88+
# `host` or `host:port`) — otherwise a same-origin caller could aim them at
89+
# hosts only this container can reach. Deliberately left unset: set it at
90+
# `docker run` time to enable PostGIS, or `*` to accept any connection string.
91+
8692
# WARNING: docker/nginx.conf's CSP allows http://localhost:* / http://127.0.0.1:*
8793
# (and ws:// equivalents) in connect-src for local-dev data sources (PMTiles/COGs
8894
# from a dev server on another port). This image is intended for local/single-user

apps/geolibre-desktop/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"tauri": "tauri",
1313
"tauri:dev": "tauri dev",
1414
"tauri:build": "node ../../scripts/tauri-build.mjs",
15-
"tauri:build:native-duckdb": "node ../../scripts/tauri-build.mjs --native-duckdb"
15+
"tauri:build:native-duckdb": "node ../../scripts/tauri-build.mjs --native-duckdb",
16+
"tauri:build:mas": "node ../../scripts/tauri-build.mjs --mas"
1617
},
1718
"dependencies": {
1819
"@anthropic-ai/sdk": "^0.115.0",
@@ -83,7 +84,7 @@
8384
"maplibre-gl-swipe": "^0.11.1",
8485
"maplibre-gl-time-slider": "^1.8.4",
8586
"maplibre-gl-usgs-lidar": "^0.11.1",
86-
"maplibre-gl-vector": "^0.10.4",
87+
"maplibre-gl-vector": "^0.10.7",
8788
"openai": "^7.0.0",
8889
"qrcode.react": "^4.2.0",
8990
"react": "^19.2.8",

apps/geolibre-desktop/src-tauri/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ crate-type = ["lib", "cdylib", "staticlib"]
1212
[features]
1313
default = []
1414
native-duckdb = ["dep:duckdb"]
15+
# Mac App Store build: compiles out every command that downloads or spawns
16+
# external code (sidecar, Jupyter, Martin, uv, external plugin installs), which
17+
# the App Sandbox and App Store guideline 2.5.2 forbid. Mutually exclusive with
18+
# native-duckdb (see the compile_error! in src/lib.rs).
19+
mas = []
1520

1621
# Optimize the release binary for size. The frontend assets dominate the binary,
1722
# but these still trim the Rust code: LTO + a single codegen unit let the linker
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<!--
4+
Merged into the generated macOS Info.plist by the Tauri bundler (all macOS
5+
builds, Developer ID and Mac App Store alike).
6+
7+
ITSAppUsesNonExemptEncryption: the app only uses HTTPS/TLS (exempt under
8+
category 5 part 2), so App Store Connect should not ask for export compliance
9+
documents on every upload.
10+
-->
11+
<plist version="1.0">
12+
<dict>
13+
<key>ITSAppUsesNonExemptEncryption</key>
14+
<false/>
15+
</dict>
16+
</plist>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<!--
4+
App Sandbox entitlements for the Mac App Store build ONLY. The Developer ID
5+
builds in release.yml do not use this file; they keep the default hardened
6+
runtime without the sandbox.
7+
8+
${APPLE_TEAM_ID} is substituted by scripts/render-mas-entitlements.sh before
9+
the build (App Store binaries must carry application-identifier and
10+
team-identifier entitlements matching the provisioning profile). The rendered
11+
Entitlements.mas.plist is gitignored.
12+
13+
Keep this list minimal: every entitlement is reviewed by App Review, and the
14+
sandbox is the reason the MAS build compiles out the Python sidecar, Jupyter,
15+
martin, and native DuckDB (see the `mas` cargo feature).
16+
17+
- network.client: map tiles, basemap styles, remote GeoJSON/COG/PMTiles,
18+
collab websocket.
19+
- network.server: the Earth Engine OAuth flow listens on a loopback port for
20+
the redirect callback.
21+
- files.user-selected.read-write: open/save project files and datasets picked
22+
through the file dialogs.
23+
-->
24+
<plist version="1.0">
25+
<dict>
26+
<key>com.apple.security.app-sandbox</key>
27+
<true/>
28+
<key>com.apple.application-identifier</key>
29+
<string>${APPLE_TEAM_ID}.org.geolibre.desktop</string>
30+
<key>com.apple.developer.team-identifier</key>
31+
<string>${APPLE_TEAM_ID}</string>
32+
<key>com.apple.security.network.client</key>
33+
<true/>
34+
<key>com.apple.security.network.server</key>
35+
<true/>
36+
<key>com.apple.security.files.user-selected.read-write</key>
37+
<true/>
38+
</dict>
39+
</plist>

0 commit comments

Comments
 (0)