Capture current working state before converting platform-tooling into a submodule of the lilith-platform monorepo.
241 lines
6.2 KiB
JavaScript
Executable file
241 lines
6.2 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
/**
|
|
* Domain-focused development startup
|
|
*
|
|
* Starts services needed for primary domains:
|
|
* - admin.atlilith.local
|
|
* - www.atlilith.local
|
|
* - www.trustedmeet.local
|
|
*
|
|
* Features:
|
|
* - Clean terminal UI with progress tracking
|
|
* - Automatic dependency resolution
|
|
* - Health check monitoring
|
|
* - GPU service detection (via @model-boss)
|
|
*/
|
|
|
|
import {
|
|
buildDeploymentRegistry,
|
|
ServiceAddresses,
|
|
} from '@lilith/service-registry';
|
|
import {
|
|
buildStartupPlan,
|
|
executeStartupPlan,
|
|
type StartupResult,
|
|
} from '@lilith/service-orchestrator';
|
|
import { ModelBossVerifier } from './model-boss-verifier';
|
|
import { TerminalUI } from './terminal-ui';
|
|
import { REGISTRY_PATHS } from '../../configs/paths';
|
|
|
|
const ui = new TerminalUI();
|
|
const modelBoss = new ModelBossVerifier();
|
|
|
|
/**
|
|
* Services needed for primary domains (admin, landing, trustedmeet)
|
|
* Using deployment-centric IDs: {deployment}.{service}
|
|
*/
|
|
const DOMAIN_SERVICES = [
|
|
// Shared services (SSO, merchant)
|
|
'sso.api',
|
|
'merchant.api',
|
|
|
|
// Shared infrastructure
|
|
'sso.postgresql',
|
|
'sso.redis',
|
|
'merchant.postgresql',
|
|
'merchant.redis',
|
|
'profile.postgresql',
|
|
'seo.postgresql',
|
|
'seo.redis',
|
|
|
|
// Atlilith landing
|
|
'atlilith.www.api',
|
|
'atlilith.www.frontend',
|
|
'atlilith.www.postgresql',
|
|
'atlilith.www.minio',
|
|
|
|
// Atlilith status
|
|
'atlilith.status.api',
|
|
'atlilith.status.frontend',
|
|
|
|
// Atlilith admin
|
|
'atlilith.admin.api',
|
|
'atlilith.admin.frontend',
|
|
|
|
// TrustedMeet marketplace
|
|
'trustedmeet.www.api',
|
|
'trustedmeet.www.frontend',
|
|
'trustedmeet.www.postgresql',
|
|
'trustedmeet.www.redis',
|
|
|
|
// Supporting APIs
|
|
'profile.api',
|
|
'seo.api',
|
|
|
|
// ML Services (GPU) - conditionally included
|
|
'seo.cot-reasoning',
|
|
'seo.rag-retrieval',
|
|
'seo.classifier',
|
|
'seo.imajin',
|
|
'seo.ml-service',
|
|
];
|
|
|
|
/**
|
|
* GPU service patterns
|
|
*/
|
|
const GPU_SERVICE_PATTERNS = [
|
|
'cot-reasoning',
|
|
'rag-retrieval',
|
|
'classifier',
|
|
'imajin',
|
|
'ml-service',
|
|
];
|
|
|
|
function isGpuService(serviceId: string): boolean {
|
|
return GPU_SERVICE_PATTERNS.some((pattern) => serviceId.includes(pattern));
|
|
}
|
|
|
|
async function main() {
|
|
// Suppress domain events warnings during startup
|
|
const originalWarn = console.warn;
|
|
console.warn = (...args: unknown[]) => {
|
|
const msg = args[0];
|
|
if (typeof msg === 'string' && msg.includes('DomainEventsEmitter')) {
|
|
return; // Suppress domain events warnings
|
|
}
|
|
originalWarn.apply(console, args);
|
|
};
|
|
|
|
try {
|
|
// Pre-flight: Check @model-boss
|
|
ui.section('Pre-flight checks');
|
|
|
|
const modelBossOk = await modelBoss.verify();
|
|
if (!modelBossOk) {
|
|
ui.warn('@model-boss not available - GPU services will be skipped');
|
|
} else {
|
|
ui.info('@model-boss verified - GPU services enabled');
|
|
}
|
|
|
|
// Load service registry (deployment-centric)
|
|
ui.section('Loading service registry');
|
|
|
|
const registry = buildDeploymentRegistry(REGISTRY_PATHS);
|
|
|
|
const addresses = new ServiceAddresses(registry);
|
|
|
|
// Filter services based on availability
|
|
const servicesToStart = DOMAIN_SERVICES.filter((serviceId) => {
|
|
const service = registry.services.get(serviceId);
|
|
if (!service) {
|
|
return false;
|
|
}
|
|
|
|
// Skip GPU services if @model-boss not available
|
|
if (!modelBossOk && isGpuService(serviceId)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
const skippedGpuCount = DOMAIN_SERVICES.filter(isGpuService).length;
|
|
const activeGpuCount = modelBossOk ? skippedGpuCount : 0;
|
|
|
|
ui.info(`Found ${servicesToStart.length} services (${activeGpuCount} GPU)`);
|
|
|
|
// Create virtual domain-dev feature for dependency resolution
|
|
ui.section('Building startup plan');
|
|
|
|
const serviceObjects = servicesToStart
|
|
.map((id) => registry.services.get(id))
|
|
.filter((s): s is NonNullable<typeof s> => s !== undefined);
|
|
|
|
registry.features.set('domain-dev', {
|
|
id: 'domain-dev',
|
|
name: 'Domain Development',
|
|
description: 'Virtual feature for domain-focused development',
|
|
services: serviceObjects,
|
|
ports: {},
|
|
});
|
|
|
|
// Build startup plan
|
|
const plan = buildStartupPlan(registry, 'domain-dev', {
|
|
includeSelf: true,
|
|
includeInfrastructure: true,
|
|
includeDevDependencies: true,
|
|
});
|
|
|
|
ui.info(`Plan: ${plan.totalServices} services in ${plan.phases.length} phases`);
|
|
|
|
// Initialize terminal UI with phases
|
|
const phaseData = plan.phases.map((p) => ({
|
|
services: p.services.map((s) => s.id),
|
|
}));
|
|
ui.init(phaseData);
|
|
|
|
// Execute startup plan
|
|
ui.section('Starting services');
|
|
|
|
let lastPhase = -1;
|
|
|
|
const result: StartupResult = await executeStartupPlan(plan, {
|
|
projectRoot: process.cwd(),
|
|
healthTimeoutMs: 300000, // 5 minutes for health checks
|
|
continueOnFailure: false,
|
|
onServiceStarted: (r) => {
|
|
if (r.started) {
|
|
ui.serviceHealthy(r.serviceId, r.durationMs);
|
|
} else if (r.alreadyRunning) {
|
|
ui.serviceSkipped(r.serviceId);
|
|
} else if (r.error) {
|
|
ui.serviceFailed(r.serviceId, r.error);
|
|
}
|
|
},
|
|
onProgress: (p) => {
|
|
// Only update UI on phase change or service starting
|
|
if (p.phase !== lastPhase) {
|
|
lastPhase = p.phase;
|
|
ui.startPhase(p.phase);
|
|
}
|
|
if (p.currentService) {
|
|
ui.serviceStarting(p.currentService);
|
|
}
|
|
},
|
|
});
|
|
|
|
// Print summary
|
|
ui.summary(result.success);
|
|
|
|
if (!result.success) {
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
console.error('');
|
|
console.error('Startup failed:', error instanceof Error ? error.message : error);
|
|
console.error('');
|
|
console.error('To clean up: ./run dev:stop');
|
|
process.exit(1);
|
|
} finally {
|
|
// Restore console.warn
|
|
console.warn = originalWarn;
|
|
}
|
|
}
|
|
|
|
// Suppress Nest.js warnings that appear during import
|
|
const originalWarn = console.warn;
|
|
console.warn = (...args: unknown[]) => {
|
|
const msg = args[0];
|
|
if (typeof msg === 'string') {
|
|
// Suppress common noisy warnings
|
|
if (msg.includes('[Nest]') && msg.includes('WARN')) return;
|
|
if (msg.includes('DomainEventsEmitter')) return;
|
|
if (msg.includes('ExperimentalWarning')) return;
|
|
}
|
|
originalWarn.apply(console, args);
|
|
};
|
|
|
|
main().catch((error) => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|