Skip to content

everything-dev

A consolidated product package for building Module Federation apps with oRPC APIs.

dev-workflow

221 linesSource

Development workflow for everything-dev projects using bos dev, bos start, and the Module Federation runtime. Use when starting dev servers, debugging hot reload, or understanding the service-descriptor architecture.

Config form: the authored config is bos.app.ts (canonical, preferred when both exist). A legacy bos.config.json is still supported for older children. Where this doc says bos.config.json for the local authored file, read "the authored config". The published artifact on FastKV keeps the key name bos.config.json.

everything-dev Development Workflow

Starting Development

shell
# Typical: start development (host mode auto-detected)
bos dev

# Isolate work
bos dev --api remote     # UI only
bos dev --ui remote      # API only
bos dev                  # Full local (rarely needed)

# Pin individual service ports (explicitly-passed flags are pinned; unset services derive from the base)
bos dev --port 3100 --api-port 3101 --ui-port 3103 --auth-port 3102 --plugin-port-start 3110

Port persistence (ADR 0012)

Port allocation is atomic block allocation: the layout derives deterministically from one base (--port N → api N+1, auth N+2, ui N+3, plugins N+10+), and the whole block is validated before anything spawns. Only explicitly-passed port flags persist to .bos/infra-state.json under devPorts — never drift. If the preferred block is occupied, the session takes the next block (+100) with a prominent notice naming the holders (live sibling sessions via registry claims; foreign processes via an lsof ownership probe), and restarts re-try the preferred base. Explicitly-passed flags are pinned: an occupied explicit port fails loudly instead of silently moving. bos kill escalates SIGTERM → 5s → SIGKILL, reaps orphaned children of dead sessions, and verifies ports actually freed; new sessions adopt-and-reap orphaned children from dead same-project sessions at boot. Test-spawned stacks never persist ports (BOS_NO_PERSIST_PORTS=1, plus NODE_ENV=test / BOS_TEST=1 are honored).

Port budgets (forward-compat for --workspaces)

The allocator accepts an optional portBudget: { min, max }. When set, pickAvailablePort skips candidates outside the budget and throws PortAllocationError if no free port is found inside it. The standalone bos dev path does not pass a budget today; a future bos dev --workspaces orchestrator will slice disjoint budgets per child project.

Port Assignments

ServiceDefaultURL
host3000http://localhost:3000
api3001http://localhost:3001
auth3002http://localhost:3002
ui3003http://localhost:3003
plugins3010+http://localhost:3010+ (incremental — one per plugin in config order)

Service-Descriptor Architecture

The orchestrator builds a ServiceDescriptorMap from the resolved config. Each descriptor defines:

  • key — service identifier (host, ui, api, auth, plugin:*)
  • source — "local" or "remote" (determines if process is spawned or URL is probed)
  • port / defaultPort — TCP port for local services
  • readinessPath — HTTP path for readiness probes (e.g., /health, /remoteEntry.js)
  • readyPatterns / errorPatterns — Regexes matched against stdout/stderr

The orchestrator:

  1. Spawns local services via pnpm run dev in each package directory
  2. Probes remote services via HTTP GET to their readiness path
  3. Tracks process state: pending → starting → ready → error
  4. Writes logs to .bos/logs/{service}.log

Hot Reload

  • UI changes: Rsbuild HMR — instant at :3003, no rebuild
  • API changes: Rspack HMR — instant at :3001, no rebuild
  • Auth / Plugin changes: No HMR — require full restart (bos kill && bos dev)
  • SSR (bos dev --ssr): source-composed SSR in the host dev process — manifests read from disk; restart not required
  • Config changes: Require host restart (bos kill && bos dev)

Contract Sync & Type Generation

Plugin types are auto-generated from the authored config via bos types gen:

shell
bos types gen   # Regenerate ui/src/lib/api-types.gen.ts and api/src/lib/plugins-types.gen.ts

When it auto-runs:

  • pnpm run typecheck
  • bos dev startup
  • bos build, bos deploy, bos publish
  • bos pluginAdd / bos pluginRemove

How plugin types are resolved:

  1. local:plugins/<name> → reads src/contract.ts directly from disk
  2. Remote URL → fetches contract types from the deployed plugin's manifest
  3. Missing local path with no URL → skipped with a warning

Source of truth: the authored config (bos.app.ts). If a plugin is listed there, its routes appear on ApiContract. If removed, TypeScript catches stale usage.

The runtime map (same generation run): .bos/generated/runtime-map.json + ui/src/lib/runtime-map.gen.ts — the composition inventory answering "who owns this screen?" and "who serves this runtime?" without grepping node_modules: ui sources, backends (host/api/auth/plugins — remote ones carry the composed plugin version + contract hash), mounts, owned routes, shadowed claims. Route-ownership and change-placement workflows live in docs/agents/runtime-map.md.

After hand-editing the authored config: Run bos types gen or restart bos dev to pick up changes.

Runtime Config Loading

The host reads BOS_RUNTIME_CONFIG at startup (resolved from the authored config by the CLI). ConfigService is an immutable Effect Layer — every service is built from that one snapshot.

Override for testing: Set BOS_RUNTIME_CONFIG env var to a JSON string or file path to bypass config loading from disk. Useful for testing with different configs without modifying the authored config.

On page refresh:

  1. Browser re-fetches HTML shell from host
  2. Host injects current config into window.__RUNTIME_CONFIG__
  3. Module Federation container re-initializes from fresh remoteEntry.js

This means a new deployment requires a host restart to pick up new URLs.

Resolved Config (.bos/bos.resolved-config.json)

bos dev and bos build write the fully-merged config to .bos/bos.resolved-config.json (gitignored). This file includes _resolved metadata with env, timestamp, and extends chain.

The authored config is NOT modified during dev. Only bos publish --deploy, bos plugin publish/add/remove, and bos upgrade write to it (bos.app.ts for TS-form projects; legacy JSON children keep their bos.config.json).

Build configs (rsbuild/rspack) read from .bos/bos.resolved-config.json first, falling back to the authored config. This allows slim child configs with extends to work correctly — the merged parent+child config is what the build sees.

Debugging

shell
bos ps                    # List running processes + ports
bos status                # Check remote health
bos info                  # Show current configuration
ls .bos/logs/             # Available log files
cat .bos/logs/api.log     # API process logs

Port Conflicts

If a port is already in use, the local service fails to start. Check what's on the port:

shell
lsof -i :3003             # Check what is using port 3003

Kill the conflicting process and retry, or stop the existing bos dev session first.

Stale PID registry

If bos kill doesn't run cleanly (e.g., terminal was closed), stale entries can linger in the global registry at ~/.cache/everything-dev/pids.json. bos ps and bos kill always prune ESRCH (dead) PIDs on read, so stale entries don't block restart — but you can wipe the file manually if you want a clean slate:

shell
rm ~/.cache/everything-dev/pids.json   # Clear global registry
bos dev                                # Start fresh

API not responding

  1. bos ps — is API running?
  2. .bos/logs/api.log — startup errors?
  3. curl http://localhost:3001/remoteEntry.js — is the entry accessible?

UI not loading

  1. Check browser console for Module Federation errors
  2. The authored config — is app.ui.development correct?
  3. Clear browser cache and hard reload (Cmd+Shift+R)

Module Federation errors

  • Verify shared dependency versions match across package.json files
  • Clear browser cache (Cmd+Shift+R)
  • Check the config URLs (authored config / FastKV) are accessible

Production Mode

shell
bos start --no-interactive                    # All remotes, production URLs
bos start --env staging --no-interactive      # Staging environment
bos start --account foo.near --domain bar.com # Load specific config

Process Management

shell
bos ps                       # List tracked development processes (cwd's entries, with role/ports)
bos kill                     # SIGTERM processes owned by cwd
bos kill --all               # SIGTERM across all config directories
bos kill --signal SIGKILL    # Force kill (cwd)
bos kill --config-dir /path  # Target a specific project

The registry lives at ~/.cache/everything-dev/pids.json, keyed by pid. Each entry stores configDir, role (standalone today; workspace-parent/workspace-child reserved for the planned bos dev --workspaces orchestrator), ports, optional budget, startedAt, and description. bos dev registers on startup and unregisters on graceful shutdown; the BOS_WORKSPACE_CHILD=1 env var suppresses standalone registration so a future parent orchestrator owns registry entries.

Process tracking uses ~/.cache/everything-dev/pids.json (global, atomic writes).

The tasks you will actually be given

"Start dev on my own ports." bos dev --port 3100 (api 3101, auth 3102, ui 3103, plugins 3110+ derive from the base). Explicit --*-port flags are pinned and persisted; derived ones are not.

"My change didn't show up." Match the change type to the reload path: UI and API hot-reload instantly; auth/plugins/config changes need bos kill && bos dev; stale generated types need pnpm run typecheck.

"API is down." bos ps → read .bos/logs/api.log → curl http://localhost:3001/remoteEntry.js — in that order.

What comes back when it fails

worddo
Infra preflight failed: …the DB preflight probes localhost ports and auto-runs docker compose up -d --wait when every failure is a down local service — if it still fails, start Postgres (pnpm run dev:postgres) and read the failure messages
PortAllocationError on an explicit portthe pinned port is occupied and allocation fails loudly — free it or pick another; it never silently moves an explicit choice
+100 port-shift notice at startupexpected drift: the preferred block was busy, holders are named in the notice, nothing is persisted — restarts re-try the preferred base
service stuck in startingprobe its readiness path directly (e.g. curl http://localhost:3003/remoteEntry.js) and read .bos/logs/<service>.log
stale registry entries blocking restartbos ps / bos kill prune dead PIDs on read; to wipe manually: rm ~/.cache/everything-dev/pids.json