docs/release-packaging.mdx
This guide provides reproducible local packaging steps for the desktop app.
One binary ships. World Monitor is the only desktop artifact, and every
variant — tech, finance, commodity, energy, happy — is selected in-app after
install and persists across restarts. There is no per-variant package, no
per-variant tag, and no per-variant download.
That is a deliberate constraint, not an omission: /api/version and
/api/download resolve /releases/latest, which returns at most one release,
so a second release line could never be served to users (#5908).
Bundle identity is controlled by a single Tauri config,
src-tauri/tauri.conf.json → World Monitor / world-monitor.
Install dependencies (this also installs the pinned Tauri CLI used by desktop scripts):
npm ci
All desktop scripts call the local tauri binary from node_modules/.bin; no runtime npx package download is required after npm ci.
If the local CLI is missing, scripts/desktop-package.mjs now fails fast with an explicit npm ci remediation message.
Before running desktop packaging in CI or managed networks, verify connectivity and proxy config:
npm ping
curl -I https://index.crates.io/
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)='
If these fail, use one of the supported remediations:
src-tauri/vendor/) + Cargo offline mode.See docs/TAURI_VALIDATION_REPORT.md for failure classification labels and troubleshooting flow.
To view script usage/help:
npm run desktop:package -- --help
.app + .dmg)npm run desktop:package:macos
# or generic runner
npm run desktop:package -- --os macos
.exe + .msi)npm run desktop:package:windows
# or generic runner
npm run desktop:package -- --os windows
Bundler targets are pinned in the Tauri config and enforced by packaging scripts:
app,dmgnsis,msiFrom src-tauri/, the project supports two packaging paths:
Use normal Cargo behavior (crates.io):
cd src-tauri
cargo generate-lockfile
cargo tauri build --config tauri.conf.json
An optional vendored source is defined in src-tauri/.cargo/config.toml. To use it, first prepare vendored crates on a machine that has registry access:
# from repository root
cargo vendor --manifest-path src-tauri/Cargo.toml src-tauri/vendor
Then enable offline mode using either method:
cd src-tauri
cargo generate-lockfile --offline --config 'source.crates-io.replace-with="vendored-sources"'
cargo tauri build --offline --config 'source.crates-io.replace-with="vendored-sources"' --config tauri.conf.json
cp src-tauri/.cargo/config.local.toml.example src-tauri/.cargo/config.local.toml
cd src-tauri
cargo generate-lockfile --offline
cargo tauri build --offline --config tauri.conf.json
For CI or internal mirrors, publish src-tauri/vendor/ as an artifact and restore it before the restricted-network build. If your organization uses an internal crates mirror instead of vendoring, point source.crates-io.replace-with to that mirror in CI-specific Cargo config and run the same build commands.
Unsigned packaging works by default.
If signing credentials are present in environment variables, Tauri will sign/notarize automatically during the same packaging commands.
Set before packaging (Developer ID signature):
export TAURI_BUNDLE_MACOS_SIGNING_IDENTITY="Developer ID Application: Your Company (TEAMID)"
export TAURI_BUNDLE_MACOS_PROVIDER_SHORT_NAME="TEAMID"
# optional alternate key accepted by Tauri tooling:
export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Company (TEAMID)"
For notarization, choose one auth method:
# Apple ID + app-specific password
export APPLE_ID="[email protected]"
export APPLE_PASSWORD="app-specific-password"
export APPLE_TEAM_ID="TEAMID"
# OR App Store Connect API key
export APPLE_API_KEY="ABC123DEFG"
export APPLE_API_ISSUER="00000000-0000-0000-0000-000000000000"
export APPLE_API_KEY_PATH="$HOME/.keys/AuthKey_ABC123DEFG.p8"
Then run either standard or explicit sign script aliases:
npm run desktop:package:macos
# or
npm run desktop:package:macos:sign
Set before packaging (PowerShell):
$env:TAURI_BUNDLE_WINDOWS_CERTIFICATE_THUMBPRINT="<CERT_THUMBPRINT>"
$env:TAURI_BUNDLE_WINDOWS_TIMESTAMP_URL="https://timestamp.digicert.com"
# optional: if using cert file + password instead of cert store
$env:TAURI_BUNDLE_WINDOWS_CERTIFICATE="C:\path\to\codesign.pfx"
$env:TAURI_BUNDLE_WINDOWS_CERTIFICATE_PASSWORD="<PFX_PASSWORD>"
Then run either standard or explicit sign script aliases:
npm run desktop:package:windows
# or
npm run desktop:package:windows:sign
src-tauri/tauri.conf.json → World Monitor / world-monitor, with
bundle.icon pointing at the shared icon set. Every packaged build carries that
identity regardless of which variant the user later switches to, so the window
title and installed app name are always World Monitor.
Adding a second tauri.*.conf.json to rebrand a variant is deliberately
rejected by tests/desktop-one-binary-model.test.mjs: nothing publishes it, and
the endpoints cannot serve it. Reopen the packaging model in #5902 first if a
branded per-variant binary becomes a real requirement.
Artifacts are produced under:
src-tauri/target/release/bundle/
Common subfolders:
app/ → macOS .appdmg/ → macOS .dmgnsis/ → Windows .exe installermsi/ → Windows .msi installer.dmg, drag app to Applications, launch..exe or .msi, launch from Start menu.World Monitor.