AGENTS.md@apps/cli/src/lib/local-server · git:20261020.8d47d7a · 2026-10-20 · sha256 fca4fe82f8ee4994
AGENTS.md@apps/cli/src/lib/local-server git:20261020.8d47d7aA
Immutable. This exact content never changes and is served at /api/v1/blob/fca4fe82f8ee4994.
# Agent Instructions — `apps/cli/src/lib/local-server` How `zitadel start`, `stop`, `status`, `logs`, `reset`, `console` and `doctor` run and find a directory's local server. The package rules in [`apps/cli/AGENTS.md`](../../../AGENTS.md) apply here too. ## The rule everything here serves Each directory runs its own server, on its own port, with its own data and admin credential, and the CLI talks only to the server a `.zitadel/local/runtime.json` names. - Commands that write or remove local state act on the working directory. Readers (`setup`, `doctor`, `console`) take the nearest record up the tree (`findUpward` in `runtime.ts`). - Never fall back to a default port. - Never trust a recorded URL or pid on its own: the record's own process or container must be the one serving (`LocalServer.resolveUrl`). ## Where things live - Commands may import any top-level module here; `internal/` is private to `server.ts` (`LocalServer`). - A command that needs more from a running server gets it from a new `LocalServer` method (and a launcher method), not code of its own. - `internal/lifecycle.ts` decides against interfaces (reuse, restart, port, failure), so its tests need no processes, sockets or real time. ## Rules that each came from a real failure - A pid is not an identity: a record's server is running only if a process in its group runs the server program on that record's data directory. List processes with `ps axww`; without `ww`, `COLUMNS` truncates the path. - Skip the identity check for a server `launch` just returned: on Linux, `ps` shows it under its parent's name until it has exec'd. - Decide whether a port is free by connecting and binding, never with `lsof` (a failed listing reads as "free"). Use `lsof` only to name a port's holder. - Claim a port with `PortProbe.reserve` before launching and hold it until the server answers, or two concurrent starts pick the same port. - Never start the server through `@zitadel/server`'s wrapper script: the recorded pid must be the process that holds the port. - Take a requested port as asked or refuse it, and check it before tearing anything down, so a refusal leaves the working server running. - Read a failed start's log before cleaning up; removing a container removes its log. - Handle the spawn `error` event: an unrunnable binary reports through it, and unhandled it crashes the CLI. ## Tests Layout and specs: [`tests/AGENTS.md`](../../../tests/AGENTS.md). - Never wait in real time: code that waits takes a `Clock` (`internal/clock.ts`), and tests pass `ManualClock` (`tests/helpers/clock.ts`). - Get a deterministic outcome by construction (the `orphaned` shape in `tests/helpers/local-server.ts`), never by sleeping. - Make no test depend on whether Docker is installed: fake Docker, or take `docker` off `PATH`. - A process standing in for a server whose data directory is checked must show its environment to `ps`, as Node does. macOS hides it for its own programs (`/bin/sleep`), so such a test passes on Linux and fails on a Mac.