Skip to content

Building from Source

mrdulasolutions edited this page May 13, 2026 · 1 revision

Building from Source

If you just want to use AOS Mail, download the latest .dmg. This page is for developers building locally.

Prerequisites

  • macOS 13+ on Apple Silicon (Intel + Linux + Windows builds are roadmap V2)
  • Node 20+node --version
  • Rust (stable channel) — install via rustup
  • Xcode Command Line Toolsxcode-select --install

Optional but useful:

  • gh CLI authenticated against mrdulasolutions/AOS-Mail (for working with PRs and releases)
  • Python 3 (for some build scripts)

Quick start

git clone https://github.com/mrdulasolutions/AOS-Mail
cd AOS-Mail
npm install
npm --prefix sidecar install
npm run dev

npm run dev invokes tauri dev, which:

  1. Builds the sidecar (prepare-node + bundle + package + runtime-modules).
  2. Builds the renderer (Vite dev server with HMR).
  3. Compiles the Rust shell (first compile is slow — 3-5 min; incremental is seconds).
  4. Launches the native window. Renderer hot-reloads on JS/CSS changes, sidecar restarts on TS changes, shell recompiles on Rust changes.

Tip: on macOS 14.4+, the bundled Node binary needs an ad-hoc codesign signature to execute, or amfid SIGKILLs it. The build pipeline handles this automatically (PR #16). If you ever see sidecar terminated: signal: Some(9) in the dev log, that's the symptom — re-running npm run build:sidecar re-applies the ad-hoc sign.

Optional .env

You can drop a .env in the repo root for build-time defaults:

MAIN_VITE_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
MAIN_VITE_GOOGLE_CLIENT_SECRET=your-client-secret
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...

Skip the .env entirely and the app will prompt you on first launch.

Commands

# Development
npm run dev                # Tauri dev (renderer + sidecar + native shell)
npm run dev:renderer       # Vite dev server only (browser, no Tauri features)
npm run build:sidecar      # Bundle + package sidecar binary
npm run build:renderer     # Vite production build
npm run build              # Full production build (sidecar + renderer + tauri)

# Quality gates (CI runs all four)
npm run typecheck          # Renderer typecheck (tsc --noEmit)
npm run typecheck:sidecar  # Sidecar typecheck
npm run lint               # ESLint on src/ + sidecar/src/
npm run lint:fix           # ESLint with autofix
npm run format             # Prettier write
npm run format:check       # Prettier check (no write)

# Tests
npm run test:sidecar       # Sidecar unit + integration (233+ tests)
npm run test:e2e           # Playwright end-to-end
npm run test:problematic   # Excluded flaky/incomplete tests
npm run test:bench         # Benchmark project

# Misc
npm run eval               # Email-analyzer eval harness (see docs/EVALS.md)

Worktree development

The maintainer uses git worktrees to run multiple Claude Code agents in parallel. If you're using worktrees:

  • New worktrees don't have gitignored files. Copy .env from your main checkout if you want OAuth credentials baked in.
  • Each worktree has its own node_modules (you'll need to npm install and npm --prefix sidecar install per worktree). After merging branches or switching worktrees, always re-run npm install before npm run dev — missing dependencies produce cryptic build failures.
  • The branch name is appended to the app titlebar in dev mode (per CLAUDE.md convention) so you can tell builds apart. Remove that change before committing.

Production build (local, unsigned)

npm run build

Produces unsigned .app and .dmg in src-tauri/target/release/bundle/. macOS Gatekeeper will block these on other machines (they'll work on your dev machine because they're locally compiled). For a real signed release, see Release Process — local signed builds are not part of the supported flow.

Where things end up

src-tauri/target/
├─ debug/                                    # tauri dev outputs
│   ├─ aos-mail                              # Rust shell binary
│   ├─ aos-mail-sidecar                      # self-extracting bash stub
│   └─ aos-mail-node                         # bundled Node binary
│
└─ <triple>/release/
    └─ bundle/
        ├─ macos/AOS Mail.app/               # the .app
        ├─ macos/AOS Mail.app.tar.gz         # updater tarball (release only)
        └─ dmg/AOS Mail_<version>.dmg        # the .dmg installer

Common build pitfalls

sidecar terminated: signal: Some(9) on first npm run dev

The bundled Node binary lost its signature. The build script ad-hoc signs it; if you somehow ended up with an unsigned binary, re-run npm run build:sidecar. See Troubleshooting.

Error: ENOENT: no such file or directory, copyfile '/opt/homebrew/bin/node' -> ...

prepare-node-binary.mjs didn't find a real Node binary. On a fresh CI runner this happens because /opt/homebrew/bin/node is a tiny shim, not the real binary. PR #11 made the script prefer process.execPath (the running interpreter), which is reliably a real Node binary. If you see this locally, your node install is unusual — point AOS_BUNDLED_NODE=/path/to/real/node at a real binary.

error: code object is not signed at all when running the .app

You built unsigned and macOS won't run it. For dev, use npm run dev (which builds debug-signed). For releases, use the GitHub Actions release workflow — not local npm run build.

npm run dev hangs at "Building" 99%

First Rust compile is slow. macos-latest GitHub runner takes ~5 min; locally on M1/M2 expect ~3-5 min. Subsequent incremental compiles are seconds.

tauri dev opens a window but it's blank

Vite dev server probably crashed. Check the terminal for the renderer-side error. Common: type error in a .tsx file. npm run typecheck to find it.

npm install errors on better-sqlite3

better-sqlite3 needs to compile its native binding against your Node version. If you switched Node versions, blow away node_modules and reinstall:

rm -rf node_modules sidecar/node_modules
npm install
npm --prefix sidecar install

Testing your changes

Before opening a PR, all four quality gates must pass:

npm run lint && \
npm run typecheck && \
npm run typecheck:sidecar && \
npm run test:sidecar

CI enforces these on every PR; running locally first saves a round-trip.

For renderer/UI changes, also smoke-test in npm run dev — TypeScript correctness doesn't catch UI regressions.

See also

Clone this wiki locally