# Install dependencies and setup hooks
yarn setup
# Start full development stack (auto-assigns unique ports per worktree)
yarn dev # or equivalently: make devyarn dev/make dev: Start full dev stack with worktree-isolated ports. A dev portal at http://localhost:9900 auto-starts showing all running stacks.yarn dev:down/make dev-down: Stop the dev stack for the current worktreemake dev-portal: Start the dev portal manually (auto-started byyarn dev)yarn lint: Run linting across all packagesyarn dev:unit: Run unit tests in watch mode (per package)yarn dev:mutation --mutate <file>: Run mutation tests (common-utils only). A surviving mutant means the tests execute that code but don't assert on it. Always scope it with--mutate; a whole-package run takes tens of minutes. See CONTRIBUTING.mdyarn seed:alerts --count N(inpackages/api): Fill this worktree's dev-slot Mongo with N alerts for testing the alerts page at scale: half on saved searches, half on dashboard tiles. Seeded dashboards and saved searches are taggedseeded;yarn seed:alerts --purgeremoves them.
.env.development: Development environment variables- Docker Compose manages ClickHouse, MongoDB, OTel Collector
- Hot reload enabled for all services in development
When multiple git worktrees need to run the dev stack simultaneously (e.g.
multiple agents or developers working in parallel), use make dev instead of
yarn dev. This automatically assigns unique ports per worktree.
- A deterministic slot (0-99) is computed from the worktree directory name (via
cksum) - Each service gets a unique port:
base + slot(see table below) - Docker Compose runs with a unique project name (
hdx-dev-<slot>) - Volume paths include the slot to prevent data corruption between worktrees
Ports are allocated in the 30100-31199 range to avoid conflicts with CI integration tests (14320-40098) and E2E tests (20320-21399).
| Service | Base Port | Range | Env Variable |
|---|---|---|---|
| API server | 30100 | 30100 - 30199 | HYPERDX_API_PORT |
| App (Next.js) | 30200 | 30200 - 30299 | HYPERDX_APP_PORT |
| OpAMP | 30300 | 30300 - 30399 | HYPERDX_OPAMP_PORT |
| MongoDB | 30400 | 30400 - 30499 | HDX_DEV_MONGO_PORT |
| ClickHouse HTTP | 30500 | 30500 - 30599 | HDX_DEV_CH_HTTP_PORT |
| ClickHouse Native | 30600 | 30600 - 30699 | HDX_DEV_CH_NATIVE_PORT |
| OTel health | 30700 | 30700 - 30799 | HDX_DEV_OTEL_HEALTH_PORT |
| OTel gRPC | 30800 | 30800 - 30899 | HDX_DEV_OTEL_GRPC_PORT |
| OTel HTTP | 30900 | 30900 - 30999 | HDX_DEV_OTEL_HTTP_PORT |
| OTel metrics | 31000 | 31000 - 31099 | HDX_DEV_OTEL_METRICS_PORT |
| OTel JSON HTTP | 31100 | 31100 - 31199 | HDX_DEV_OTEL_JSON_HTTP_PORT |
The dev portal is a centralized web dashboard that discovers all running worktree stacks by inspecting Docker container labels and slot files.
# Start the portal (runs on fixed port 9900)
make dev-portal
# Open in browser
open http://localhost:9900The portal auto-refreshes every 3 seconds and shows each worktree's:
- Branch name and slot number
- All services with status (running/stopped) and clickable port links
- Separate cards for each active worktree
# Use a specific slot instead of the auto-computed one
HDX_DEV_SLOT=5 make dev- Unit Tests: Jest with TypeScript support
- Integration Tests: Jest with database fixtures
- Frontend Testing: React Testing Library + Jest
- E2E Testing: Playwright (frontend) and Custom smoke tests with BATS (ingestion)
- TDD Approach: Write tests before implementation for new features
- Test organization: Tests co-located with source files in
__tests__/directories - Mocking: MSW for API mocking in frontend tests
- Database testing: Isolated test databases with fixtures
For integration testing:
# Build dependencies (run once before first test run)
make dev-int-build
# Run API integration tests (spins up Docker services, runs tests, tears down)
make dev-int FILE=<TEST_FILE_NAME>
# Run common-utils integration tests
make dev-int-common-utils FILE=<TEST_FILE_NAME>Multi-agent / worktree support:
The make dev-int command automatically assigns unique Docker ports per
worktree directory, so multiple agents can run integration tests in parallel
without port conflicts.
- A deterministic slot (0-99) is computed from the worktree directory name
- Each slot gets its own Docker Compose project name and port range
- Override the slot manually:
make dev-int HDX_CI_SLOT=5 FILE=alerts - The slot and assigned ports are printed when
dev-intstarts
Port mapping (base + slot):
| Service | Default port (slot 0) | Variable |
|---|---|---|
| ClickHouse HTTP | 18123 | HDX_CI_CH_PORT |
| MongoDB | 39999 | HDX_CI_MONGO_PORT |
| API test server | 19000 | HDX_CI_API_PORT |
| OpAMP | 14320 | HDX_CI_OPAMP_PORT |
CI Testing Notes:
- Uses separate Docker Compose configuration (
docker-compose.ci.yml) - Isolated test environment with unique
-p int-<slot>project name - Includes all necessary services (ClickHouse, MongoDB, OTel Collector)
- Tests run against real database instances for accurate integration testing
E2E tests use the same slot-based isolation pattern as integration tests, with their own dedicated port range (20320-21399) so they can run simultaneously with both the dev stack and CI integration tests.
E2E port mapping (base + slot):
| Service | Base Port | Range | Env Variable |
|---|---|---|---|
| OpAMP | 20320 | 20320 - 20419 | HDX_E2E_OPAMP_PORT |
| ClickHouse HTTP | 20500 | 20500 - 20599 | HDX_E2E_CH_PORT |
| ClickHouse Native | 20600 | 20600 - 20699 | HDX_E2E_CH_NATIVE_PORT |
| API server | 21000 | 21000 - 21099 | HDX_E2E_API_PORT |
| MongoDB | 21100 | 21100 - 21199 | HDX_E2E_MONGO_PORT |
| App (local) | 21200 | 21200 - 21299 | HDX_E2E_APP_LOCAL_PORT |
| App (fullstack) | 21300 | 21300 - 21399 | HDX_E2E_APP_PORT |
# Run all E2E tests
make e2e
# Run a specific test file (dev mode: hot reload, containers kept running)
make dev-e2e FILE=navigation
# Run a specific test by grep pattern
make dev-e2e FILE=navigation GREP="help menu"
# Grep across all files
make dev-e2e GREP="should navigate"
# Open HTML report after tests finish (screenshots, traces, step-by-step)
make dev-e2e FILE=navigation REPORT=1
# Or call the script directly for more control
./scripts/test-e2e.sh --ui --last-failed
# Override the slot manually
HDX_E2E_SLOT=5 ./scripts/test-e2e.sh- A deterministic slot (0-99) is computed from the worktree directory name
- Each slot gets its own Docker Compose project name (
e2e-<slot>) and port range - The slot and assigned ports are printed when E2E tests start
Port range safety: E2E has its own dedicated port range (20320-21399) that does not overlap with CI integration tests (14320-40098) or the dev stack (30100-31199), so all three can run simultaneously from the same worktree.
- API First: Define API endpoints and data models
- Database Models: Create/update Mongoose schemas and ClickHouse queries
- Frontend Integration: Build UI components and integrate with API
- Testing: Add unit and integration tests
- Documentation: Update relevant docs
- Check browser and server console output for errors, warnings, or relevant logs
- Add targeted logging to trace execution and variable states
- For persistent issues, check
fixes/directory for documented solutions - Document complex fixes in
fixes/directory with descriptive filenames
The project uses Husky + lint-staged to automatically run:
- Prettier for formatting
- ESLint for linting
- API doc generation (for external API changes)
These run automatically on git commit for staged files.
If you need to manually lint:
# Per-package linting with auto-fix
cd packages/app && yarn run lint:fix
cd packages/api && yarn run lint:fix
cd packages/common-utils && yarn lint:fix
# Check all packages
yarn run lint- Config:
packages/api/src/config.ts,packages/app/next.config.mjs,docker-compose.dev.yml - Models:
packages/api/src/models/ - API Routes:
packages/api/src/routers/ - Controllers:
packages/api/src/controllers/ - Pages:
packages/app/pages/ - Components:
packages/app/src/ - Shared Utils:
packages/common-utils/src/— no root barrel, so imports are deep@hyperdx/common-utils/dist/...paths. Seecode_style.mdfor the module map and the grep-first rule before you add a helper.
The hyperdx-private Vercel project deploys the @hyperdx/app Next.js app
for preview environments. Normally /api/* requests proxy to a separately
deployed API service (SERVER_URL). For PR previews we instead inline the
entire Express API into the Next.js serverless function so a single Vercel
deployment serves both the app and the API.
packages/api/src/serverless.tsexposes the Express app fromapi-app.tsas a stateless(req, res) => Promise<void>handler. It lazily connects to MongoDB on the first invocation and caches the connection across warm invocations.packages/app/pages/api/[...all].tsbranches on theHDX_PREVIEW_INLINE_APIenv var. Whentrue, itrequire()s the compiled serverless handler and dispatches directly. Otherwise it falls back to the existing http-proxy-middleware path used by Docker/standalone builds.packages/app/next.config.mjsadds a webpackexternalsrule that marks@hyperdx/apias a CommonJS external unlessHDX_PREVIEW_INLINE_APIistrue. This keeps production app builds (Docker fullstack image, standalone Next output) byte-for-byte equivalent to before — they never bundle passport-saml, mongoose, AWS SDK, etc.vercel.jsondeclares the repo-root build commands so Vercel buildscommon-utils→api→appin order.
| Key | Notes |
|---|---|
HDX_PREVIEW_INLINE_API |
true — turns on the inline path |
MONGO_URI |
Preview MongoDB connection string |
EXPRESS_SESSION_SECRET |
Random 32+ char string |
DISABLED_AUTH_METHODS |
google,saml (avoids per-preview OAuth callback URL config) |
AI_PROVIDER, AI_API_KEY, AI_MODEL_NAME |
Optional, only if AI features should work |
FRONTEND_URL |
Optional. Leave unset to use host-only cookies on the preview URL |
Production env vars on the same project must leave HDX_PREVIEW_INLINE_API
unset (or false) so production deployments keep using the proxy path.
- No background tasks. Scheduled jobs (
packages/api/src/tasks/, includingCheckAlertTask) are not run in preview deployments. - No alerts. Alert evaluation depends on the cron task runner.
- No OPAMP server. The OPAMP control-plane server (
APP_TYPE=opamp) is not started in preview. The serverless entrypoint assertsAPP_TYPE=apiand refuses to boot otherwise. - Function size. The bundled function should stay under Vercel's 50 MB
unzipped limit. If it grows, mark heavy deps (
@aws-sdk/*,@node-saml/passport-saml, AI SDKs) asserverExternalPackagesinnext.config.mjs. - Cold-start latency. First request after idle pays ~500–1500 ms for the initial Mongo connection.
- Shared preview MongoDB. Unless you partition by database name, all preview deployments share the same Mongo state.