Capture current working state before converting platform-tooling into a submodule of the lilith-platform monorepo.
252 lines
7.7 KiB
TypeScript
252 lines
7.7 KiB
TypeScript
#!/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);
|
||
});
|