Development Environment Setup
This guide covers local development setup for Mesh Client, including cloning, prerequisites, test harness tooling, and OS-specific troubleshooting.
Shared Requirements and Tooling
These requirements apply to all platforms.
1) Required software
- Git
- Node.js 22.12.0+ (match CI;
CONTRIBUTING.mdrecommends the same) - pnpm 10+
- Python 3 +
pip(needed for MkDocs documentation build and yamllint)
Verify:
git --version
node --version
pnpm --version
MkDocs (documentation) tooling
Docs are built with MkDocs Material.
- Create and activate a local virtual environment (recommended on macOS/Homebrew Python because of PEP 668 externally managed environments):
- macOS/Linux:
python3 -m venv .venvsource .venv/bin/activate
- Windows PowerShell:
py -3 -m venv .venv.\.venv\Scripts\Activate.ps1
- Install the docs dependencies:
pnpm run docs:install- or (manual):
python3 -m pip install -r docs/requirements.txt - Build locally:
pnpm run docs:build- Preview locally:
pnpm run docs:serve
If pnpm run docs:install fails with externally-managed-environment, activate .venv and rerun.
2) Clone and install
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
If you are updating from an older clone, use a clean install when troubleshooting native module issues:
rm -rf node_modules package-lock.json
pnpm install
3) Run the app
- Dev mode (hot reload):
pnpm run dev - Production-like local start:
pnpm start
Common pnpm commands
Use these from the repository root:
# App run/build
pnpm run dev
pnpm start
pnpm run build
# Platform packaging (binary artifacts in release/)
pnpm run dist:mac
pnpm run dist:linux
pnpm run dist:win
# Quality checks
pnpm run test:run
pnpm run lint
pnpm run typecheck
pnpm run format:check
# Docs
pnpm run docs:install
pnpm run docs:build
pnpm run docs:serve
All Scripts Reference
Complete reference of all npm scripts in package.json, organized by category.
Build
| Script | Description |
|---|---|
build |
Full production build: main (minified) + preload + renderer |
build:main |
Build main process (no minify) → dist-electron/main/index.js |
build:main:prod |
Build main process (minified) → dist-electron/main/index.js |
build:main:meta |
Build main with metadata JSON (no minify) → dist-electron/main/metafile.json |
build:main:minify-meta |
Build main with metadata JSON (minified) → dist-electron/main/meta.json |
build:main:size |
Print main bundle size |
build:preload |
Build preload script → dist-electron/preload/index.js |
build:renderer |
Build renderer (React app) via Vite → dist/ |
Run
| Script | Description |
|---|---|
dev |
Hot-reload dev mode: builds main/preload in watch mode + Vite dev server + Electron |
start |
Production-like local start: runs build then launches Electron |
electron:open |
Launch Electron (requires prior build) |
trace-deprecation |
Run with Node deprecation traces enabled |
Package (distributables)
| Script | Description |
|---|---|
dist |
Build for current platform |
dist:mac |
Build macOS .dmg + .zip → release/ |
dist:mac:publish |
Build macOS and upload to release server |
dist:linux |
Build Linux .AppImage + .deb → release/ |
dist:linux:publish |
Build Linux and upload to release server |
dist:win |
Build Windows .exe installer → release/ |
dist:win:publish |
Build Windows and upload to release server |
Test
| Script | Description |
|---|---|
test |
Run tests in watch mode |
test:run |
Run tests once (CI mode) |
test:verbose |
Run tests with verbose output |
Lint / Format
| Script | Description |
|---|---|
lint |
Run ESLint (type-aware) |
lint:fix |
Run ESLint with auto-fix |
lint:md |
Run markdownlint-cli2 on all .md files |
format |
Format all code via Prettier |
format:check |
Check formatting without fixing |
Typecheck
| Script | Description |
|---|---|
typecheck |
TypeScript check: renderer + main process |
Quality Checks
| Script | Description |
|---|---|
check:log-injection |
Detect unsanitized user data in log calls |
check:db-migrations |
Verify SQLite migrations are valid |
check:ipc-contract |
Verify IPC channel contracts between main/preload/renderer |
Documentation
| Script | Description |
|---|---|
docs:install |
Install MkDocs Python dependencies |
docs:build |
Build static docs to site/ |
docs:serve |
Serve docs locally with live reload |
Setup / Helpers
| Script | Description |
|---|---|
setup:actionlint |
Install actionlint for GitHub workflow linting |
setup:build-deps |
Install native build dependencies |
setup:dialout |
Add user to dialout group for serial port access (Linux) |
rebuild |
Rebuild native Node modules for Electron |
Lifecycle (automatic)
| Script | Description |
|---|---|
preinstall |
Enforce pnpm as package manager |
postinstall |
Rebuild native modules + apply patches |
prepare |
Enable git hooks |
predist |
Dedupe packages before packaging |
Dependabot dependency updates
Automated dependency updates are configured in .github/dependabot.yml:
- Schedule: Weekly on Saturdays
- npm dependencies: Grouped PRs —
electronseparate, all other npm deps together - GitHub Actions: Grouped into one PR
Testing Dependabot PRs locally:
Always use pnpm to test dependabot PRs:
git checkout <dependabot-branch>
pnpm install --frozen-lockfile
pnpm run build
pnpm run test:run
Do not use npm install — it creates a package-lock.json and may not respect pnpm's lockfile format.
4) Test harness setup and local quality checks
This section is the project test harness setup.
Installed via pnpm install (from package.json):
vitestand renderer/main test dependencieseslinttypescriptprettierprettier-plugin-shmarkdownlint-cli2
Not installed by npm (install separately when needed):
actionlint(recommended for workflow linting; runpnpm run setup:actionlintor install system-wide)yamllint(required for YAML linting; install viapip install yamllintorbrew install yamllinton macOS)dockerandact(only if you run GitHub Actions locally)- Python 3 +
venv+ MkDocs Python deps (for docs checks/builds)
Run these quality checks before opening a PR:
pnpm run test:run
pnpm run lint
pnpm run lint:md
pnpm run typecheck
pnpm run format:check
Other useful test commands:
pnpm test(watch mode)pnpm run test:verbose(verbose failures)
5) Building a distributable
Use the platform-specific packaging command:
pnpm run dist:mac # macOS -> .dmg + .zip in release/
pnpm run dist:linux # Linux -> .AppImage + .deb in release/
pnpm run dist:win # Windows -> .exe installer in release/
Output goes to the release/ directory.
Build analysis
To analyze the main process bundle size and composition:
pnpm run build:main:minify-meta
This generates dist-electron/main/meta.json. Upload this file to esbuild's online analyzer to visualize:
- Bundle size by dependency
- Code that could be externalized
- Minification effectiveness
6) Git hooks and pre-commit behavior
After pnpm install, repo hooks are enabled via core.hooksPath and pre-commit runs checks (format, lint, typecheck, audit, actionlint, tests).
Emergency bypass is available:
git commit --no-verify
Use this only as a temporary escape hatch, then run the skipped checks manually as soon as possible.
7) CI workflow tooling (optional but recommended)
- Docker (required to run
actlocally) - act: run GitHub Actions locally with Linux amd64 parity:
act --container-architecture linux/amd64
- actionlint: required for local pre-commit if workflow files are touched.
8) Helper scripts (auto-install where possible)
These scripts try to install optional tooling automatically. If they fail (for example, missing sudo/admin rights), follow the manual steps in this doc instead.
- Install
actionlint(used by the git pre-commit hook): pnpm run setup:actionlint- This installs into
.githooks/binso the hook can find it. - Install
yamllint(required by the git pre-commit hook): - Install manually via pip:
pip install yamllint - macOS alternative:
brew install yamllint - Linux alternative:
sudo apt install yamllint(Debian/Ubuntu) orsudo dnf install yamllint(Fedora) - Install native build dependencies:
pnpm run setup:build-deps- Linux/macOS: attempts to install what native builds need (requires sudo where applicable).
- Windows: prints a message to install Visual Studio Build Tools manually.
- (Linux only) Fix serial port permissions:
pnpm run setup:dialout- Adds your user to the
dialoutgroup (requires sudo + re-login).
9) Optional editor/tooling
- VS Code (or Cursor) with TypeScript + ESLint support
- Prettier editor extension (optional convenience; repository already defines formatting rules)
- React DevTools for renderer debugging
macOS
Install prerequisites
- Install Git (Xcode CLT includes it):
bash xcode-select --install - Install Node 22 (22.12.0+ recommended via nvm) and npm:
bash curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm install 22 nvm use 22
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Bluetooth permissions
On first BLE connection, macOS prompts for Bluetooth access. If denied accidentally:
- Go to System Settings > Privacy & Security > Bluetooth
- Enable access for Mesh-Client
macOS release-download note (not required for source development)
If a downloaded app reports "Mesh-client is damaged and can't be opened", remove quarantine:
xattr -r -d com.apple.quarantine /Applications/Mesh-client.app
Windows
Install prerequisites
- Install Git and Node.js (winget primary path):
powershell winget install git.git winget install OpenJS.NodeJS - Allow npm script execution in current user scope:
powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - Install Visual Studio Build Tools with Desktop development with C++ workload.
- Install Python 3 and ensure it is on PATH:
powershell winget install Python.Python.3.12If needed, set npm Python path explicitly:powershell npm config set python "C:\\Path\\To\\python.exe"
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Serial device driver reminder
If serial ports do not appear, install the right USB UART driver (for example CH340/CH341, CP210x, or FTDI).
Troubleshooting
"Could not find any Visual Studio installation to use"
Cause: outdated node-gyp resolution or missing C++ build tools workload.
Fix:
- Install/confirm Visual Studio Build Tools with Desktop C++ workload.
- Upgrade node-gyp:
bash pnpm install node-gyp@latest -g pnpm install node-gyp@latest --save-dev - Restart terminal and rerun:
bash pnpm install
"Could not find any Python installation to use"
Cause: Python missing or not on PATH for node-gyp.
Fix:
- Install Python 3 and add it to PATH.
- Restart terminal.
- Retry
pnpm install(orpnpm run dist:win). - If still failing, set Python path with
npm config set python ....
dist:win fails with path spaces or EPERM
- Prefer a path without spaces (for example
C:\dev\mesh-client) - Close running Electron/Node processes before rebuild
- Run:
bash pnpm run rebuild pnpm run dist:win
Linux
Install prerequisites
Install Node 22 (22.12.0+ recommended), make, and C++ build tools (g++/gcc-c++) with native build dependencies.
Debian/Ubuntu:
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
sudo apt install build-essential
sudo apt install python3 libnspr4 libnss3
Fedora/RedHat:
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
sudo dnf install @development-tools
sudo dnf install python3 nspr nss
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Serial permissions
Add your user to dialout:
sudo usermod -a -G dialout $USER
Log out/in after changing groups.
Linux Bluetooth (BLE)
Linux uses Web Bluetooth (Chromium's built-in BLE API) instead of @stoprocent/noble. This approach:
- Requires no setcap/setuid workaround scripts
- Requires the user to select a device from the in-app Bluetooth picker (backed by Chromium's chooser event)
- Requires a user gesture (button click) to trigger device selection
The app automatically enables --enable-experimental-web-platform-features on Linux at startup.
Bluetooth Pairing on Linux
Web Bluetooth may invoke the Electron pairing handler during GATT connect. Behavior differs by protocol:
- Meshtastic: On the first Chromium
providePinrequest, the client tries the standard Meshtastic PIN123456. If that fails, you are prompted to enter the PIN manually. - MeshCore: The MeshCore session does not auto-submit
123456. When Chromium asks for a PIN, you enter the random code shown on the radio.
MeshCore and OS-level pairing (Linux / BlueZ): A stable GATT session usually requires a bond in BlueZ first. After you choose a device in the in-app picker, the client runs bluetoothctl info <MAC>. If the device is not paired (Paired: no) or not yet known to the adapter, the UI prompts for the PIN on the radio and runs bluetooth-pair (main-process bluetoothctl pairing) before resolving the pending Web Bluetooth selection. If Paired: yes already, connection continues without that step.
Handshake retries reuse the same granted Web Bluetooth device (navigator.bluetooth.getDevices()) so the second attempt does not call requestDevice() without a user gesture.
If you encounter pairing issues (e.g., "Connection attempt failed" or device was previously paired with wrong PIN):
- Use the "Remove & Re-pair Device" button in the app
- Or manually remove via
bluetoothctl:bash bluetoothctl # Inside bluetoothctl: remove XX:XX:XX:XX:XX:XX # Replace with your device MAC # Then re-pair from the app - If the device still won't connect, power cycle Bluetooth:
bash bluetoothctl power off bluetoothctl power on
Linux launch notes
The supported dev and local run flows are:
pnpm run dev
pnpm start
ARM (for example Raspberry Pi) may also require:
sudo apt install zlib1g-dev libfuse2
sudo sysctl -w kernel.unprivileged_userns_clone=1
Troubleshooting
npm 11: Unknown env config "devdir"
npm config delete devdir
npm config delete devdir --global
unset npm_config_devdir NPM_CONFIG_DEVDIR
SIGILL during pnpm install (electron exited with signal SIGILL)
Install without running Electron rebuild first:
MESHTASTIC_SKIP_ELECTRON_REBUILD=1 pnpm install
Then run rebuild on a host where Electron executes correctly:
pnpm run rebuild
SIGSEGV on startup (electron exited with signal SIGSEGV)
Use:
pnpm run build && npx electron . --disable-gpu
Or:
pnpm run electron:open -- --disable-gpu
Optional persistent mitigation:
export MESH_CLIENT_DISABLE_GPU=1ELECTRON_OZONE_PLATFORM_HINT=x11 pnpm run electron:open
Serial: serial_io_handler.cc:147 Failed to open serial port: FILE_ERROR_ACCESS_DENIED
- Ensure user is in
dialout. - Re-login.
- Verify with:
bash groups - If missing, create and activate:
bash sudo groupadd dialout sudo usermod -a -G dialout $USER newgrp dialout