IFM Build Pipeline
How the IFM distribution pieces are built, packaged, installed, and published.
This repository builds and packages the products that exist today:
| Product | Source | Artifact |
|---|---|---|
@ifm/sdk | packages/sdk/ | npm tarball (ifm-sdk-<v>.tgz) |
| ifm-cli | crates/cli/ | native ifm binary (bin/ifm-<os>-<arch>; cargo install ifm-cli, Homebrew) |
| Station (web) | packages/sdk/examples/station/ | static site web/ifm-station/ |
| Relay (web) | dashboard packages/sdk/examples/relay/ | static site web/ifm-relay/ |
| Listener (web) | packages/sdk/examples/listener/ | static site web/ifm-listener/ |
| Landing | packages/landing/ | static site web/ifm-landing/ |
| ifm-node | native ifm binary (crates/cli) | bin/ifm-<os>-<arch> (Docker packaging: see packaging.md) |
| IFM Station (desktop) | packages/ifm-station/ | .dmg, .msi, .AppImage, .deb, .rpm |
| IFM Relay Station (desktop) | packages/ifm-relay-station/ | .dmg, .msi, .AppImage, .deb, .rpm |
| IFM Radio Listener (desktop) | packages/ifm-radio-listener/ | .dmg, .msi, .AppImage, .deb, .rpm |
| IFM Mobile Radio Listener (PWA) | packages/ifm-pwa/ | Static site (HTML/JS/WASM + SW + manifest) |
Scripts
| Script | Purpose |
|---|---|
bun scripts/start.mjs | Super-easy local setup — runs relay + Station/Relay/Listener + Landing together |
bun scripts/build.mjs | Build every piece in place (--skip-rust to skip the native binary) |
bun scripts/release.mjs | Build + package everything into releases/v<version>/ with checksums |
bash scripts/install.sh | Universal CLI installer (npm / local release / GitHub Releases) |
npm run dev | Alias for start.mjs |
npm run release | Alias for release.mjs |
Quick Start
# 1. One command — run the whole stack locally (web apps)
npm run dev
# Station http://localhost:5173/
# Relay http://localhost:5174/ hub at ws://localhost:8790/
# Listener http://localhost:5175/
# Landing http://localhost:5176/
# 2. Build all distribution pieces
npm run build:all # or: bun scripts/build.mjs --all
# Includes: SDK, CLI, web demos, landing, desktop apps, PWA
# 3. Produce a release (tarballs + static sites + native binary + desktop + PWA + checksums)
npm run release # → releases/v0.1.0/
# 4. Install the CLI from that release
bash releases/v0.1.0/install.sh --local releases/v0.1.0
ifm init && ifm connect && ifm tune 91.700
# 5. Install desktop apps (one command each)
curl -fsSL https://ifm.sh/install-station.sh | bash
curl -fsSL https://ifm.sh/install-relay-station.sh | bash
curl -fsSL https://ifm.sh/install-radio-listener.sh | bash
# 6. Access PWA
# Open https://ifm.sh/pwa in mobile browser → "Add to Home Screen"Release Layout
bun scripts/release.mjs produces releases/v<version>/:
releases/v0.1.0/
├── npm/
│ ├── ifm-sdk-0.1.0.tgz # @ifm/sdk (npm publish-ready)
│ └── ifm-cli-0.1.0.tgz # @ifm/cli (npm publish-ready)
├── web/
│ ├── ifm-station/ # Station static build
│ ├── ifm-relay/ # Relay dashboard static build
│ ├── ifm-listener/ # Listener static build
│ └── ifm-landing/ # Landing static build
├── bin/
│ └── ifm-darwin-arm64 # native ifm binary (platform-tagged)
├── desktop/
│ ├── ifm-station-darwin-arm64.dmg
│ ├── ifm-station-darwin-x64.dmg
│ ├── ifm-station-windows-x64.msi
│ ├── ifm-station-windows-arm64.msi
│ ├── ifm-station-linux-x64.AppImage
│ ├── ifm-station-linux-arm64.AppImage
│ ├── ifm-station_0.1.0_amd64.deb
│ ├── ifm-station-0.1.0.x86_64.rpm
│ ├── ifm-relay-station-darwin-arm64.dmg
│ ├── ... (same matrix for relay-station)
│ ├── ifm-radio-listener-darwin-arm64.dmg
│ ├── ... (same matrix for radio-listener)
│ └── checksums-desktop.txt
├── pwa/
│ ├── index.html
│ ├── manifest.json
│ ├── sw.js
│ ├── assets/
│ │ ├── index-<hash>.js
│ │ ├── index-<hash>.css
│ │ ├── ifm-sdk-<hash>.js
│ │ └── ifm-sdk_bg-<hash>.wasm
│ ├── icons/
│ └── version.json
├── install.sh # universal installer
├── checksums.txt # SHA-256 of every artifact
└── version.json # version + product manifestPublishing (one command away)
Publishing requires credentials; the pipeline is built so the commands are trivial to run once you have them.
npm (@ifm/sdk)
npm login # once
cd packages/sdk && npm publish --access public
# or, from a release:
npm publish releases/v0.1.0/npm/ifm-sdk-0.1.0.tgzThe package is pre-configured in its package.json (files, exports, engines) for publishing as-is. The CLI ships as the native ifm binary (crates/cli) — see CLI reference.
GitHub Release
Tagging triggers .github/workflows/release.yml, which builds everything and attaches the artifacts to the release:
git tag -s v0.1.0 -m "Release v0.1.0"
git push origin v0.1.0npm from CI
Add an NPM_TOKEN repository secret (a token with publish scope). The next v* tag then publishes both packages automatically; without the secret, the publish job is skipped and only the GitHub Release is produced.
Channels
channels.md defines the promotion ladder (nightly → beta → stable → lts). The scripts accept a version override (--version 0.2.0-beta.1) and everything flows through the same release.mjs path — only the tag/version and dist-tags differ. There is no automatic channel promotion yet; that is a Release Manager decision per channels.md.
Version Sync (automatic)
A release version is applied to every version-bearing manifest automatically (scripts/sync-versions.mjs):
- all
package.jsonfiles (root + sdk, cli, ifm-pwa, the three desktop apps) - each desktop app's
src-tauri/tauri.conf.json— this is what the INSTALLED apps report (CFBundleShortVersionString, About dialog, installer filename). Without it, a release could bumppackage.jsonwhile the bundles keep advertising an old version.
The sync runs in all three release paths:
bun scripts/build-release.mjs 0.3.2-alpha— bumps both families up frontbun scripts/release.mjs --version 0.3.2-alpha— bumps both before buildingbun scripts/build.mjs --desktop— syncs each app'stauri.conf.jsonfrom its ownpackage.jsonright before bundling, so a plain desktop build can never ship a bundle with a stale version
All syncs are idempotent (they only write when the version actually changes).
CI
| Workflow | Trigger | What it does |
|---|---|---|
.github/workflows/ci.yml | push / PR | compiles SDK+CLI, runs CLI self test, builds web demos + landing, builds desktop apps, builds PWA, cargo test --workspace |
.github/workflows/release.yml | v* tags | builds everything (SDK, CLI, web, desktop, PWA, native binary), attaches artifacts to GitHub Release, publishes npm when NPM_TOKEN configured |
.github/workflows/desktop.yml | push / PR (desktop paths) | builds Tauri apps for all platforms, runs smoke tests |
.github/workflows/pwa.yml | push / PR (pwa paths) | builds PWA, runs Lighthouse CI, deploys preview to Cloudflare Pages |
Hosting the web apps
The demo apps are designed for the local flow above (dev servers + the vite /relay proxy). To serve the built static sites with a working relay, put the hub behind a reverse proxy that forwards /relay (HTTP probe + WebSocket upgrade) to the hub port — the same pattern the dev servers use. This is what a future ifm-pwa deployment (packaging.md §ifm-pwa) would ride on; no Cloudflare/Netlify configs are generated yet.