platform-docs/technical/SERVICE_DEV.md

9.5 KiB

Service Development CLI

DEPRECATED (2026-01-25): This document describes the legacy feature-centric pnpm dev:start approach using @lilith/service-registry. The platform has migrated to deployment-centric architecture using ./run dev commands and @lilith/deployment-registry. See:

  • docs/architecture/deployments.md - New deployment architecture
  • ./run dev:trustedmeet, ./run dev:status, etc. - New dev commands

Auto-start dependencies declared in services.yaml when running feature dev servers.


Quick Start

# Start a feature with all its dependencies
pnpm dev:start analytics

# Preview what would start (dry run)
pnpm dev:start seo --dry-run

# List all available features
pnpm dev:start --list

# Stop services we started
pnpm dev:start --stop

Automatic Frontend Dependency Startup

React/Vite frontends automatically start their backend dependencies via the @lilith/vite-plugin-dependency-startup plugin:

// codebase/features/analytics/frontend-users/vite.config.ts
import { dependencyStartupPlugin } from '@lilith/vite-plugin-dependency-startup';

export default defineConfig({
  plugins: [
    dependencyStartupPlugin({ feature: 'analytics' }),  // MUST be first plugin
    react(),
    versionPlugin({ appName: 'Analytics Users' }),
  ],
});

What happens when you run pnpm dev:

  1. Vite loads the config file
  2. Plugin runs in configResolved hook (BEFORE server starts)
  3. Plugin imports @lilith/service-registry and reads services.yaml
  4. Plugin builds startup plan (analytics.api, analytics.postgresql, analytics.redis)
  5. Plugin executes plan: starts missing services, accepts running ones
  6. Plugin waits for health checks
  7. Vite dev server starts (port 5173)

Benefits:

  • Zero manual coordination - just run pnpm dev for the frontend
  • Idempotent - works whether dependencies are running or not
  • Automatic CI detection - skips startup in E2E/CI environments
  • Same orchestration logic as pnpm dev:start (shared via @lilith/service-registry)

When to use pnpm dev:start vs plugin:

  • Plugin (automatic): For React/Vite frontends - built into dev server startup
  • pnpm dev:start (manual): For backend APIs, ML services, or when you want explicit control

How It Works

Dependency Resolution

Each feature declares dependencies in its services.yaml:

# codebase/features/analytics/services.yaml
services:
  - id: api
    type: api
    dependencies:
      - infrastructure.postgresql
      - analytics.postgresql
      - analytics.redis

The CLI resolves the full dependency graph using topological sort, ensuring services start in the correct order.

Startup Phases

Dependencies are grouped into phases for parallel startup:

Phase 1: infrastructure.postgresql, infrastructure.redis  (no deps)
Phase 2: ml.image-generation, truth-validation.api       (depend on Phase 1)
Phase 3: image-generator.api                             (depends on Phase 2)

Services within the same phase can start concurrently.

Deduplication

Before starting each service, the CLI checks if it's already running:

  1. PID file check - Did we start this service in a previous session?
  2. HTTP health check - For API/ML services with /health endpoint
  3. Docker container check - For PostgreSQL/Redis infrastructure
  4. TCP port check - Fallback for any service with a port

This prevents duplicate instances when services are shared across developers or already running.


CLI Reference

Commands

Command Description
pnpm dev:start <feature> Start feature with all dependencies
pnpm dev:start --list List available features
pnpm dev:start --stop Stop services we started (PID-tracked)

Options

Option Description
--dry-run Show what would start without starting
--no-deps Skip dependency startup, start only the feature
--verbose Show detailed progress output
--timeout=N Health check timeout in seconds (default: 60)

Examples

# Start analytics with verbose output
pnpm dev:start analytics --verbose

# Preview SEO dependencies
pnpm dev:start seo --dry-run

# Start marketplace without its dependencies
pnpm dev:start marketplace --no-deps

# Stop all services we started
pnpm dev:start --stop

Service Status Detection

Detection Priority

  1. PID File (/tmp/lilith-services/$USER/*.pid)

    • Tracks services we started
    • Used for cleanup
  2. HTTP Health Check

    • For services with healthCheck.type: http
    • Checks configured health endpoint (default: /health)
  3. Docker Container

    • For postgresql and redis service types
    • Checks docker ps for running containers
  4. TCP Port

    • Fallback for any service with a port
    • Simple connection test

Output Legend

Icon Meaning
(gray) Already running (skipped)
(yellow) Would start (dry-run)
(green) Started successfully
(red) Failed to start

Architecture

Package: @lilith/service-registry

The CLI uses the @lilith/service-registry package (v3.1.0+) which provides:

import {
  initServiceRegistry,
  buildStartupPlan,
  executeStartupPlan,
  getRunningServices,
  stopOurServices,
} from '@lilith/service-registry';

// Initialize from services directory
const registry = initServiceRegistry({
  servicesPath: 'infrastructure/services/features',
  portsPath: 'infrastructure/ports.yaml',
  strict: false,
});

// Build startup plan with dependency resolution
const plan = buildStartupPlan(registry.getRegistry(), 'seo', {
  includeSelf: false,
  includeInfrastructure: true,
});

// Check what's already running
const { running, notRunning } = await getRunningServices(plan.orderedServices);

// Execute startup with progress callbacks
const result = await executeStartupPlan(plan, {
  healthTimeoutMs: 60000,
  projectRoot: '/path/to/lilith-platform',
  onProgress: (progress) => console.log(progress.currentService),
  onServiceStarted: (result) => console.log(result.serviceId, result.started),
});

Files

File Purpose
infrastructure/scripts/services/service-dev.ts CLI entry point
@lilith/service-registry/src/orchestrator.ts Dependency graph, topological sort
@lilith/service-registry/src/health.ts Service status detection
@lilith/service-registry/src/startup.ts Service execution, PID tracking

PID Tracking

Services we start are tracked via PID files:

/tmp/lilith-services/
  └── $USER/
      ├── analytics.api.pid
      ├── seo.api.pid
      └── image-generator.api.pid

This allows:

  • Cleanup of only services we started
  • Detection of services from previous sessions
  • Multi-developer support (per-user PID directories)

services.yaml Reference

Structure

feature:
  id: analytics
  name: Analytics
  description: Usage metrics and reporting
  owner: platform-core

ports:
  api: 3012
  postgresql: 5434
  redis: 6381

services:
  - id: api
    name: Analytics API
    type: api
    port: 3012
    entrypoint: codebase/features/analytics/backend-api
    healthCheck:
      type: http
      path: /health
    dependencies:
      - infrastructure.postgresql
      - analytics.postgresql
      - analytics.redis

  - id: postgresql
    name: Analytics Database
    type: postgresql
    port: 5434

  - id: redis
    name: Analytics Cache
    type: redis
    port: 6381

Service Types

Type Start Method Health Check
api pnpm start:dev in entrypoint HTTP /health
frontend pnpm dev in entrypoint HTTP response
ml Python uvicorn HTTP /health
postgresql Docker container TCP port
redis Docker container TCP port
worker pnpm start:worker Process check
websocket WebSocket server TCP port

Dependency Format

Dependencies use the format feature.service:

dependencies:
  - infrastructure.postgresql   # Shared PostgreSQL
  - infrastructure.redis       # Shared Redis
  - truth-validation.api       # Another feature's API
  - seo.redis                  # Same-feature service

Troubleshooting

Service Not Starting

  1. Check if port is already in use:

    ss -tlnp | grep :PORT
    
  2. Check service logs:

    # API services
    cat /tmp/lilith-services/$USER/service-name.log
    
    # Docker services
    docker logs container-name
    
  3. Verify health endpoint:

    curl http://localhost:PORT/health
    

Cleanup Issues

If --stop doesn't clean up properly:

# Manual cleanup
rm -rf /tmp/lilith-services/$USER/*.pid

# Kill specific port
fuser -k PORT/tcp

Missing Dependencies

If dependencies aren't detected:

  1. Verify services.yaml is symlinked in infrastructure/services/features/
  2. Check dependency format is feature.service
  3. Run pnpm services:validate to check configuration


Status: Active Last Updated: 2026-01-10