platform-tooling/run/scripts/verify-services.ts
Quinn Ftw 85621b287e chore: snapshot before monorepo consolidation
Capture current working state before converting platform-tooling
into a submodule of the lilith-platform monorepo.
2026-01-29 07:04:39 -08:00

252 lines
7.7 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env tsx
/**
* Verify Service Configuration
*
* Validates that all services in DOMAIN_SERVICES and TOOLS_SERVICES:
* 1. Exist in the service registry
* 2. Have valid start commands (not Docker-only services)
* 3. Have correct entrypoints that exist on disk
*
* Run: pnpm services:verify (from project root)
* Or: tsx tooling/run/scripts/verify-services.ts
*/
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { DOMAIN_SERVICES, TOOLS_SERVICES } from '../core/services';
import { PATHS, REGISTRY_PATHS } from '../../configs/paths';
// =============================================================================
// Types
// =============================================================================
interface ServiceDefinition {
id: string;
type: string;
entrypoint?: string;
startCommand?: string;
port?: number;
}
interface ValidationResult {
serviceId: string;
valid: boolean;
errors: string[];
warnings: string[];
}
// =============================================================================
// Service Type Validation
// =============================================================================
/**
* Service types that can be started as host services
*/
const HOST_SERVICE_TYPES = new Set([
'api',
'frontend',
'ml-service',
'worker',
'gateway',
]);
/**
* Service types that are Docker-only (should NOT be in DOMAIN_SERVICES)
*/
const DOCKER_ONLY_TYPES = new Set([
'postgresql',
'redis',
'minio',
'elasticsearch',
'meilisearch',
'rabbitmq',
'kafka',
]);
// =============================================================================
// Registry Loading
// =============================================================================
async function loadRegistry(): Promise<Map<string, ServiceDefinition>> {
const registry = new Map<string, ServiceDefinition>();
const projectRoot = PATHS.root;
try {
const { buildDeploymentRegistry } = await import('@lilith/service-registry');
const reg = buildDeploymentRegistry(REGISTRY_PATHS);
for (const [id, service] of reg.services) {
registry.set(id, {
id,
type: service.type,
entrypoint: service.entrypoint,
startCommand: service.startCommand,
port: service.port,
});
}
} catch (err) {
console.error('Failed to load service registry:', err);
process.exit(1);
}
return registry;
}
// =============================================================================
// Validation
// =============================================================================
function validateService(
serviceId: string,
registry: Map<string, ServiceDefinition>,
projectRoot: string
): ValidationResult {
const result: ValidationResult = {
serviceId,
valid: true,
errors: [],
warnings: [],
};
const service = registry.get(serviceId);
// Check if service exists in registry
if (!service) {
result.valid = false;
result.errors.push(`Service not found in registry`);
return result;
}
// Check if service type is Docker-only
if (DOCKER_ONLY_TYPES.has(service.type)) {
result.valid = false;
result.errors.push(
`Service type '${service.type}' is Docker-only - should not be in host service list`
);
return result;
}
// Check if service type is startable
if (!HOST_SERVICE_TYPES.has(service.type) && !service.startCommand) {
result.valid = false;
result.errors.push(
`Service type '${service.type}' is not a known host service type and has no startCommand`
);
}
// Check entrypoint exists (for services with entrypoints)
if (service.entrypoint) {
const entrypointPath = join(projectRoot, service.entrypoint);
if (!existsSync(entrypointPath)) {
result.valid = false;
result.errors.push(`Entrypoint not found: ${service.entrypoint}`);
} else {
// Check for package.json with dev script
const packageJsonPath = join(entrypointPath, 'package.json');
if (!existsSync(packageJsonPath)) {
result.warnings.push(`No package.json in entrypoint directory`);
}
}
}
// Check port is defined
if (!service.port) {
result.warnings.push(`No port defined`);
}
return result;
}
// =============================================================================
// Main
// =============================================================================
async function main(): Promise<void> {
console.log('🔍 Verifying service configuration...\n');
const projectRoot = PATHS.root;
const registry = await loadRegistry();
console.log(`📦 Loaded ${registry.size} services from registry\n`);
const allServices = new Set([...DOMAIN_SERVICES, ...TOOLS_SERVICES]);
const results: ValidationResult[] = [];
// Validate each service
for (const serviceId of allServices) {
const result = validateService(serviceId, registry, projectRoot);
results.push(result);
}
// Report results
const valid = results.filter((r) => r.valid);
const invalid = results.filter((r) => !r.valid);
const withWarnings = results.filter((r) => r.warnings.length > 0);
console.log('═══════════════════════════════════════════════════════════════');
console.log(' SERVICE VALIDATION REPORT');
console.log('═══════════════════════════════════════════════════════════════\n');
if (invalid.length > 0) {
console.log('❌ ERRORS:\n');
for (const result of invalid) {
console.log(` ${result.serviceId}:`);
for (const error of result.errors) {
console.log(`${error}`);
}
console.log();
}
}
if (withWarnings.length > 0) {
console.log('⚠️ WARNINGS:\n');
for (const result of withWarnings) {
if (result.warnings.length > 0) {
console.log(` ${result.serviceId}:`);
for (const warning of result.warnings) {
console.log(`${warning}`);
}
console.log();
}
}
}
console.log('───────────────────────────────────────────────────────────────');
console.log(` Total services: ${allServices.size}`);
console.log(` ✅ Valid: ${valid.length}`);
console.log(` ❌ Invalid: ${invalid.length}`);
console.log(` ⚠️ Warnings: ${withWarnings.length}`);
console.log('───────────────────────────────────────────────────────────────\n');
// Check for services in registry that might be missing from lists
const listedServices = new Set([...DOMAIN_SERVICES, ...TOOLS_SERVICES]);
const hostServices = Array.from(registry.values()).filter(
(s) => HOST_SERVICE_TYPES.has(s.type) || s.startCommand
);
const missingFromLists = hostServices.filter(
(s) => !listedServices.has(s.id)
);
if (missingFromLists.length > 0) {
console.log(' HOST SERVICES NOT IN ANY LIST:\n');
for (const service of missingFromLists) {
console.log(`${service.id} (${service.type})`);
}
console.log();
}
// Exit with error if any invalid
if (invalid.length > 0) {
console.log('❌ Validation failed. Fix errors above before running ./run dev\n');
process.exit(1);
}
console.log('✅ All services validated successfully!\n');
}
main().catch((err) => {
console.error('Verification failed:', err);
process.exit(1);
});