Files
Qiusheng Wu 3060b53081 feat(desktop): add a sandboxed Mac App Store build variant (#1581)
* feat(desktop): add a sandboxed Mac App Store build variant

Adds a `mas` build of GeoLibre Desktop that satisfies App Store review
rules (App Sandbox, guideline 2.5.2), alongside the existing Developer ID
notarized builds:

- `mas` cargo feature compiles out everything that downloads or spawns
  external code: the Python sidecar, Jupyter server, martin tile server,
  the uv bootstrap, and external plugin installation. Invoke-compatible
  stubs keep the command registry unchanged; `compile_error!` forbids
  combining with `native-duckdb` (runtime extension loading).
- `GEOLIBRE_MAS_BUILD=1` frontend flag (Vite define, DCE-friendly) hides
  the sidecar-only UI (AI Segmentation, PostgreSQL, File Geodatabase,
  plugin marketplace) and routes processing dialogs to their client-side
  engines (Whitebox WASM, browser conversions, client raster tools,
  Turf/Pyodide vector tools, CereusDB SQL). The Notebook panel keeps
  working via JupyterLite, which stays in the MAS bundle.
- App Sandbox entitlements template + render script, tauri.mas.conf.json
  overlay (Apple Distribution signing, embedded provisioning profile, no
  sidecar resources), and `npm run tauri:build:mas`.
- Shapefile companions: the sandbox denies reading unselected siblings of
  a picked .shp, so the MAS build matches .dbf/.prj/.shx/.cpg out of the
  same multi-selection instead.
- mas-store.yml workflow (manual dispatch, mirrors msix-store.yml):
  builds the universal sandboxed app, verifies the sandbox entitlement,
  and uploads a signed .pkg artifact for Transporter submission.
- docs/mac-app-store.md documents the variant, its limitations, the
  required secrets, and the submission steps.

Also fixes cargo check --features native-duckdb, broken by the locked
duckdb crate making Value non-exhaustive.

* Address review feedback

- Widen the MAS shapefile companion set to the Rust command's full
  16-extension list and make companion extensions selectable in the MAS
  file dialog (they were excluded by the vector filter, so the
  multi-select fallback could not receive them).
- Add APPLE_MAS_CERTIFICATE_PASSWORD to the workflow's secrets check so
  a missing password fails with the actionable message.
- Set persist-credentials: false on the mas-store.yml checkout.
- Pin dtolnay/rust-toolchain to the current stable commit SHA; the job
  later holds signing certificates in its keychain.
- Replace envsubst with sed in render-mas-entitlements.sh (gettext is
  not preinstalled on macOS runners) and validate the team ID format.
- Filter MAS-hidden Add Data kinds from the command palette and reject
  them in the OPEN_ADD_DATA_EVENT handler.
- Disable the Segmentation form inputs under MAS via formUnavailable,
  kept separate from webUnavailable so the desktop-download CTA does not
  appear on a desktop build.
- Move the hardcoded raster sidecar message into i18n
  (toolbar.rasterTool.needsDesktopSidecar).
- Document the security-scoped-bookmark project-restore limitation in
  docs/mac-app-store.md.
2026-07-31 00:35:05 -04:00

58 lines
2.0 KiB
JavaScript

import { spawnSync } from "node:child_process";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const userArgs = process.argv.slice(2);
const nativeDuckDb = userArgs.includes("--native-duckdb");
const mas = userArgs.includes("--mas");
const tauriArgs = userArgs.filter((arg) => arg !== "--native-duckdb" && arg !== "--mas");
const buildArgs =
tauriArgs.length === 0 && process.platform === "linux" ? ["--bundles", "deb,rpm"] : tauriArgs;
if (nativeDuckDb && mas) {
// Native DuckDB fetches its spatial extension at runtime, which the Mac App
// Store build must not do (App Sandbox + guideline 2.5.2); the MAS build
// falls back to DuckDB-WASM in the webview.
console.error("--native-duckdb cannot be combined with --mas.");
process.exit(1);
}
if (nativeDuckDb) {
buildArgs.push("--features", "native-duckdb");
}
if (mas) {
if (process.platform !== "darwin") {
console.error("--mas builds the Mac App Store variant and requires macOS.");
process.exit(1);
}
// The `mas` cargo feature compiles out everything that downloads or spawns
// external code (Python sidecar, Jupyter, martin, uv bootstrap); the config
// overlay adds App Sandbox entitlements, the embedded provisioning profile,
// and drops the bundled sidecar resources.
buildArgs.push("--features", "mas");
buildArgs.push("--config", "src-tauri/tauri.mas.conf.json");
}
const result = spawnSync(
"npm",
["run", "tauri", "-w", "geolibre-desktop", "--", "build", ...buildArgs],
{
cwd: repoRoot,
shell: process.platform === "win32",
stdio: "inherit",
env: mas
? {
...process.env,
// Strips the in-app update flow (Store-policy parity with the MSIX
// Store build) and hides UI backed by the compiled-out services.
GEOLIBRE_STORE_BUILD: "1",
GEOLIBRE_MAS_BUILD: "1",
}
: process.env,
},
);
process.exit(result.status ?? 1);