UNPKG

everything-dev

Version:

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

219 lines (158 loc) • 11 kB
--- name: dev-workflow description: 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. metadata: sources: "packages/everything-dev/src/service-descriptor.ts,packages/everything-dev/src/orchestrator.ts,packages/everything-dev/src/dev-logs.ts,packages/everything-dev/src/dev-session.ts,packages/everything-dev/src/process-registry.ts,packages/everything-dev/src/app.ts,packages/everything-dev/src/infra/preflight.ts,packages/everything-dev/src/dev-program.ts" --- > **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 ```bash # 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 | Service | Default | URL | |---------|---------|-----| | host | 3000 | http://localhost:3000 | | api | 3001 | http://localhost:3001 | | auth | 3002 | http://localhost:3002 | | ui | 3003 | http://localhost:3003 | | plugins | 3010+ | 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`: ```bash 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. **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 sync` 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 ```bash 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: ```bash 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: ```bash 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 ```bash 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 ```bash 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 | word | do | |---|---| | `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 port | the 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 startup | expected drift: the preferred block was busy, holders are named in the notice, nothing is persisted — restarts re-try the preferred base | | service stuck in `starting` | probe its readiness path directly (e.g. `curl http://localhost:3003/remoteEntry.js`) and read `.bos/logs/<service>.log` | | stale registry entries blocking restart | `bos ps` / `bos kill` prune dead PIDs on read; to wipe manually: `rm ~/.cache/everything-dev/pids.json` |