@stamhoofd/cli
v2.134.0
Published
`stam` is the development CLI for this repository. It manages local setup, shared services, app processes, development configuration, database helpers, SSO helpers, tests, and cleanup.
Readme
Stamhoofd CLI
stam is the development CLI for this repository. It manages local setup, shared services, app processes, development configuration, database helpers, SSO helpers, tests, and cleanup.
Quick Start
yarn install
yarn stam setup
yarn stam dev allRun yarn stam setup shell to install the CLI alias stam in your .zshrc or .bashrc (that removes the need to type yarn and the need to always run commands in the project root).
Open the dashboard URL printed by the CLI, or run yarn stam status to see active services, instances, URLs, and credentials.
Run yarn stam --help or yarn stam <topic> --help for command help.
For CLI Users
Commands
| Area | Command | Purpose |
| ----------- | ------------------------------------ | ------------------------------------------------------------------------ |
| Build | yarn stam build | Build shared packages and all app packages for the selected environment. |
| Setup | yarn stam setup | Check the machine and offer recommended setup fixes. |
| Setup | yarn stam setup node | Install the Node.js version from .nvmrc using fnm or nvm. |
| Setup | yarn stam setup dns | Configure local .stamhoofd DNS. |
| Setup | yarn stam setup cert | Trust the local Caddy HTTPS authority. |
| Development | yarn stam dev all | Start shared services and the full app stack. |
| Development | yarn stam dev backend | Start backend apps for the current instance. |
| Development | yarn stam dev frontend | Start frontend apps only. |
| Development | yarn stam dev instance | Start this workspace instance using shared services. |
| Services | yarn stam services up | Start shared Docker services. |
| Services | yarn stam services status | Show shared service status. |
| Services | yarn stam services logs | Tail shared service logs. |
| Services | yarn stam services restart | Restart shared services with interactive progress output. |
| Services | yarn stam services down | Stop shared services. |
| Services | yarn stam services stop | Alias for yarn stam services down. |
| Status | yarn stam status | Show shared services, active instances, URLs, and credentials. |
| Config | yarn stam config explain | Explain resolved instance config. |
| Config | yarn stam config print | Print resolved domains and backend environment values as JSON. |
| Database | yarn stam db shell | Open a MySQL shell for the current local database. |
| Database | yarn stam db migrate | Build shared packages and run migrations. |
| SSO | yarn stam sso config | Print local SSO client, user, and issuer settings. |
| SSO | yarn stam sso start <redirect-uri> | Start Keycloak and import the local realm. |
| SSO | yarn stam sso logs | Tail Keycloak logs. |
| SSO | yarn stam sso stop | Stop the local Keycloak container. |
| Tests | yarn stam test unit | Run unit tests with isolated MySQL. |
| Tests | yarn stam test e2e | Run Playwright tests. |
| Tests | yarn stam test all --ci | Run unit and E2E tests in CI mode. |
| Checks | yarn stam check lint | Run ESLint across the monorepo. |
| Checks | yarn stam check typecheck | Run TypeScript checks across the monorepo. |
| Checks | yarn stam check all | Run build, lint, typecheck, unit tests, and E2E tests. |
| Cleanup | yarn stam clean build | Remove build artifacts. |
| Cleanup | yarn stam clean db | Drop the selected local MySQL database after confirmation. |
| Cleanup | yarn stam clean sso | Stop the local SSO server. |
| Cleanup | yarn stam clean services | Stop shared services. |
| Cleanup | yarn stam clean all | Clean build artifacts and stop shared services. |
Development Configuration
shared/cli owns local development configuration. Backend and frontend development builds load domains, ports, database settings, storage settings, and app environment values from @stamhoofd/cli.
The main config contract lives in src/config/development-config.ts. Keep local-development settings there first, then consume the resolved config from commands, workflows, Caddy, SSO, Stripe, status output, or app bootstrapping.
Inspect the current config with:
yarn stam config explain
yarn stam config print
yarn stam config print --env keeoInstances And Ports
The CLI infers an instance from the workspace, selected environment, and optional overrides.
Useful flags:
--env <name>selects the development environment. The default isstamhoofd.--name <name>overrides the inferred instance name.--verboseprints extra diagnostics and commands.
Useful environment variables:
STAMHOOFD_WORKSPACE_NAMEoverrides the workspace name.STAMHOOFD_PRIMARY_INSTANCE=1forces primary-instance behavior.STAMHOOFD_INSTANCE_PREFIXoverrides the domain prefix.STAMHOOFD_PORT_OFFSEToverrides the deterministic port offset.STAMHOOFD_DOMAINoverrides the shared local domain, defaulting tostamhoofd.MYSQL_PORToverrides the local MySQL host port, defaulting to3307.STAMHOOFD_MYSQL_INNODB_BUFFER_POOL_SIZEtunes the MySQL container InnoDB buffer pool size (e.g.512M,1G), defaulting to4G.STAMHOOFD_MYSQL_INNODB_BUFFER_POOL_INSTANCEStunes the MySQL container InnoDB buffer pool instances defaulting to4.STAMHOOFD_MYSQL_SORT_BUFFER_SIZEtunes the MySQL container sort buffer size (e.g.8M), defaulting to2M.PUBLIC_IP: Publish DNS records to your computers public IP address, and make Caddy listen on 0.0.0.0 instead of localhost. Useful for testing on local devices. E.g.PUBLIC_IP=192.168.1.7 stam services restartPUBLIC_IP=192.168.1.7 stam dev all
The primary stamhoofd instance uses base ports. With Git, the primary instance is the first worktree in git worktree list --porcelain. With jj, it is the first workspace in jj workspace list. Other worktrees and workspaces get deterministic offsets based on the workspace name so multiple workspaces can run on the same machine without changing databases when branches change.
When needing a heavy duty MySQL instance to test migrations, you can restart MySQL using:
STAMHOOFD_MYSQL_INNODB_BUFFER_POOL_INSTANCES=10 \
STAMHOOFD_MYSQL_SORT_BUFFER_SIZE=64M \
STAMHOOFD_MYSQL_INNODB_BUFFER_POOL_SIZE=16G \
stam services restartShared Services
Shared services run as Docker containers:
- MySQL:
stamhoofd-mysql - MailDev:
stamhoofd-maildev - RustFS:
stamhoofd-rustfs - CoreDNS:
stamhoofd-coredns - Caddy:
stamhoofd-caddy
The setup is intentionally different where Docker behaves differently:
- Linux runs Caddy on unprivileged ports
8080/8443and usessudo iptablesredirects from80/443. - Linux configures split DNS through
systemd-resolved, pointing.stamhoofdto CoreDNS on127.0.0.1:1053. - macOS uses Docker Desktop bridge networking for Caddy, publishes
80/443directly, and proxies back to host app ports throughhost.docker.internal. - macOS configures
/etc/resolver/stamhoofdwithnameserver 127.0.0.1, so only thestamhoofdresolver domain and its subdomains use local CoreDNS on port53. - MySQL listens on host port
3307by default and still uses container port3306.
Start and inspect them with:
yarn stam services up
yarn stam services status
yarn stam services logsStop them with:
yarn stam services downLocal SSO
Use yarn stam sso config to print the issuer, client credentials, test user, and example command.
Start Keycloak with the redirect URI copied from the app:
yarn stam sso start "https://<organization-id>.api.stamhoofd/openid/callback"The command imports a local realm with the printed client and test user.
Tests
yarn stam test runs build:shared first and only starts a MySQL container when a selected package needs one. That container runs off a data volume that persists between runs (so the data dir + migrations are reused, mapped DB_PORT), and it is shut down after the run. Both the container and volume are namespaced per worktree so runs don't collide:
yarn stam test unit # every unit package (excludes Playwright)
yarn stam test api # one package (api, models, sql, structures, renderer, redirecter, queues, utility, sgv, object-differ, eslint)
yarn stam test unit SomeFile # filter by filename across all packages
yarn stam test structures bundle-discounts # package + filename filter
yarn stam test structures -t 'partial test name' # package + test-name filter (passed to vitest -t)
yarn stam test api --skip-build # skip the automatic build:shared step
yarn stam test api --clear # reset the test database (drop its volume) before runningRun Playwright tests with:
yarn stam test e2e # full build + suite
yarn stam test e2e --grep @tag # only tests matching a name/tag (playwright --grep)
yarn stam test e2e --grep @tag --skip-build # skip build:shared + API/frontend rebuild (only test files changed)Like the unit runs, the e2e command keeps its data volume between runs (so migrated worker databases are reused) and shuts the container down afterward. Reset that persistent e2e database with:
yarn stam test e2e --clearUse --workers <number> to override Playwright's default worker count for a run.
Use --extra to include tests tagged @extra.
Run the full validation flow with:
yarn stam check allFor CLI Maintainers
The sections below are for contributors working on shared/cli itself rather than only using the CLI.
Architecture Overview
shared/cli is organized around a small set of responsibilities:
src/commands/: oclif command entrypoints that parse flags and dispatch work.src/workflows/: multi-step flows such as machine setup or starting a full development session.src/services/: shared Docker service abstractions, service definitions, and service orchestration.src/config/: development configuration and generated infrastructure config such as Caddy routing.src/context/: workspace, instance, and port resolution.src/runtime/: process execution, output rendering, manifests, help rendering, and external CLI helpers.
The intended flow is:
- Commands parse user input.
- Commands create a
CliContext. - Commands call workflows, runtime helpers, or services.
- Workflows orchestrate long-running behavior, service startup, manifests, and output.
- Services encapsulate Docker-specific behavior.
Try to keep that direction intact. Commands should stay thin, workflows should own coordination, and runtime utilities should stay generic enough to be reused from multiple commands.
Runtime Concepts
Some runtime concepts show up across many commands and are worth understanding before changing behavior.
Instance Inference
The CLI computes a CliContext from:
- the repository root
- the selected environment
- the workspace name
- optional overrides such as
--nameor environment variables
That context decides the instance name, domain prefix, whether the instance is considered primary, and the port offset used by local apps.
Port Allocation
Base ports come from src/context/ports.ts.
For secondary instances, src/context/instance.ts and src/context/port-allocation.ts assign deterministic offsets so multiple clones of the repository can run at once. If a computed range is occupied, the CLI steps to the next bucket until it finds a free range or fails with a clear error.
Manifests
The CLI writes JSON manifests under .development/cli/generated to describe what is currently running.
- instance manifests let
stam statusand Caddy discover active local instances - the shared services manifest records that shared infrastructure was started
When changing startup or shutdown behavior, keep manifest creation and cleanup in sync or the CLI will show stale instances or stale routes.
Output Flow
Long-running commands do not write directly to console.log unless they intentionally bypass the CLI output system. Instead they use runtime helpers so status lines, live output, tables, and command logging can coexist without corrupting terminal output.
When changing interactive output, check src/runtime/live-output.ts, src/runtime/output-target.ts, and src/runtime/ux.ts together.
Service Model
Shared infrastructure is modeled through ServiceDefinition in src/services/service.ts.
The common patterns are:
DockerService: base class for services that start throughdocker runSharedDockerService: convenience base for shared services without per-command optionsmanager.ts: orchestration for status, start, stop, restart, log tailing, and interactive tablesregistry.ts: the ordered list of shared services used by the CLI
The normal lifecycle is:
- Read current status.
- Optionally prepare files or derived config.
- Decide whether an existing container can be reused.
- Stop the old container if needed.
- Run setup hooks.
- Start the container.
- Run post-start hooks.
- Return any environment variables needed by callers.
When adding a service, keep Docker-specific behavior inside the service class instead of spreading it across commands or workflows.
As a rule of thumb:
- add a new service definition in
src/services/definitions/ - register it in
src/services/registry.tsif it is part of the shared baseline - use
startServicesInteractiveorrestartServicesInteractivewhen the command should show progress for multiple services - use
startServicesorstopServiceswhen the caller already controls user-facing output
Development Config Contract
src/config/development-config.ts is the main contract exported by @stamhoofd/cli for the rest of the monorepo.
It is responsible for resolving:
- local domains
- port numbers
- backend environment variables
- frontend and backend app environment objects
- environment-specific presets such as platform name or user mode
Prefer putting local-development defaults there first and reading the resolved config elsewhere instead of re-deriving values in commands, services, app bootstrapping, or generated infrastructure config.
Good candidates for this file:
- anything that should stay consistent across app startup, status output, Caddy routing, and service helpers
- environment-specific local development behavior
- values consumed by backend or frontend dev builds through
@stamhoofd/cli
Bad candidates for this file:
- one-off command-only formatting
- transient workflow state
- Docker lifecycle logic
- generic runtime helpers that are unrelated to development configuration
Working On The CLI
yarn install builds shared/cli so normal CLI startup stays fast. When changing CLI source code, use stam-dev to rebuild before running:
yarn stam-dev --helpFor CLI-only changes, run:
yarn --cwd shared/cli -s build
yarn --cwd shared/cli -s lint
yarn --cwd shared/cli -s testCLI tests live next to source files as *.test.ts.
After changing CLI behavior, validate at least the package-local checks:
yarn --cwd shared/cli -s test
yarn --cwd shared/cli -s buildFor command-surface changes, it is also useful to compare the generated help output with the README:
yarn stam --help
yarn stam services --help
yarn stam clean --helpTroubleshooting
Use yarn stam setup first. It checks Node, Docker, Caddy, DNS, and certificate trust.
The active Node.js version differs from
.nvmrcRun
source .development/install-node.sh. The script uses fnm or nvm, whichever is available.stam statusalso reports this mismatch, andstam devwill stop before starting processes with the wrong version.
If that does not tell you enough, use the first matching case below.
- DNS names like
dashboard.stamhoofddo not resolve: Runyarn stam setup dns, then retryyarn stam setup check. yarn stam setupreports missing privileged port redirects on Linux: Let the setup command apply the recommendedsudo iptablesrules, then retry.- HTTPS works badly or the browser does not trust local certificates:
Run
yarn stam setup cert, then retryyarn stam setup check. - Docker commands fail or services do not start:
Start Docker, retry the command, and use
yarn stam services statusto confirm which service is still down. - Caddy fails to reload or URLs do not open locally:
Check
yarn stam services status, then tryyarn stam services restart. - URLs, instance names, or ports look wrong:
Run
yarn stam status,yarn stam config explain, and check whether--env,--name, or environment variables such asSTAMHOOFD_WORKSPACE_NAMEare overriding the inferred instance. - Two workspaces conflict on ports:
Check whether
STAMHOOFD_PORT_OFFSETis forcing the same offset in multiple clones. Otherwise rerun the command and let automatic port allocation pick another bucket. - Local database state is broken:
Use
yarn stam clean dbfor the selected instance oryarn stam clean allif generated state is broadly stale. - Shared services state is broken:
Use
yarn stam services restart, oryarn stam clean servicesif volumes or generated service files need to be cleared manually. - SSO redirect or Keycloak issues appear locally:
Re-run
yarn stam sso config, make sure the redirect URI still ends in/openid/callback, then restart SSO withyarn stam sso start "<redirect-uri>". - Stale build or type errors keep appearing after code changes:
Run
yarn build:shared, then retry the CLI command or app startup flow.
