UNPKG

iop

Version:
257 lines 9.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigValidator = void 0; exports.validateConfig = validateConfig; exports.formatValidationErrors = formatValidationErrors; const port_checker_1 = require("./port-checker"); class ConfigValidator { constructor(config) { this.config = config; } /** * Validates the entire configuration for common issues */ validate() { const errors = []; // Check for reserved names const reservedNameErrors = this.checkReservedNames(); errors.push(...reservedNameErrors); // Check for port conflicts within the same project const portConflicts = this.checkIntraProjectPortConflicts(); errors.push(...portConflicts); // Check for invalid port configurations const invalidPorts = this.checkInvalidPorts(); errors.push(...invalidPorts); return errors; } /** * Checks for reserved command names being used as app/service names */ checkReservedNames() { const errors = []; const allEntries = this.getAllEntries(); for (const entry of allEntries) { if (ConfigValidator.RESERVED_NAMES.includes(entry.name)) { errors.push({ type: "reserved_name", message: `App/service name "${entry.name}" is reserved and cannot be used`, entries: [entry.name], server: entry.server, suggestions: this.generateReservedNameSuggestions(entry.name), }); } } return errors; } /** * Checks for port conflicts within the same project configuration */ checkIntraProjectPortConflicts() { const errors = []; const serverPortUsage = new Map(); // Get all entries (apps and services) const allEntries = this.getAllEntries(); // Group port usage by server for (const entry of allEntries) { if (!entry.ports) continue; const serverName = entry.server; if (!serverPortUsage.has(serverName)) { serverPortUsage.set(serverName, new Map()); } const serverPorts = serverPortUsage.get(serverName); try { const portMappings = (0, port_checker_1.parsePortMappings)(entry.ports); for (const mapping of portMappings) { const hostPort = mapping.hostPort; if (!serverPorts.has(hostPort)) { serverPorts.set(hostPort, []); } serverPorts.get(hostPort).push(entry.name); } } catch (error) { // Handle invalid port format - will be caught by checkInvalidPorts continue; } } // Check for conflicts for (const [serverName, portMap] of serverPortUsage) { for (const [port, entries] of portMap) { if (entries.length > 1) { errors.push({ type: "port_conflict", message: `Port ${port} is used by multiple services on server ${serverName}`, entries, server: serverName, port, suggestions: this.generatePortConflictSuggestions(port, entries, serverName), }); } } } return errors; } /** * Checks for invalid port configurations */ checkInvalidPorts() { const errors = []; const allEntries = this.getAllEntries(); for (const entry of allEntries) { if (!entry.ports) continue; for (const portSpec of entry.ports) { if (!this.isValidPortSpec(portSpec)) { errors.push({ type: "invalid_port", message: `Invalid port specification: "${portSpec}" in ${entry.name}`, entries: [entry.name], server: entry.server, suggestions: [ "Valid port formats:", '- "80:80" (host:container)', '- "8080:80" (different host and container ports)', '- "127.0.0.1:8080:80" (bind to specific IP)', '- "80" (same port for host and container)', '- "80/tcp" or "80/udp" (specify protocol)', ], }); } } } return errors; } /** * Validates a port specification format */ isValidPortSpec(portSpec) { // Valid formats: // "80", "80/tcp", "80/udp" // "80:80", "80:80/tcp", "80:80/udp" // "127.0.0.1:80:80", "127.0.0.1:80:80/tcp" const patterns = [ /^(\d+)(?:\/(tcp|udp))?$/, // "80" or "80/tcp" /^(\d+):(\d+)(?:\/(tcp|udp))?$/, // "80:80" or "80:80/tcp" /^(\d+\.\d+\.\d+\.\d+):(\d+):(\d+)(?:\/(tcp|udp))?$/, // "127.0.0.1:80:80" ]; return patterns.some((pattern) => pattern.test(portSpec)); } /** * Gets all entries from the services configuration */ getAllEntries() { const entries = []; // Add services if (this.config.services) { if (Array.isArray(this.config.services)) { entries.push(...this.config.services); } else { // Convert object format to array for (const [name, service] of Object.entries(this.config.services)) { entries.push({ ...service, name }); } } } return entries; } /** * Generates suggestions for resolving reserved name conflicts */ generateReservedNameSuggestions(reservedName) { const suggestions = [ `The name "${reservedName}" is reserved for CLI commands.`, "", "Reserved names: " + ConfigValidator.RESERVED_NAMES.join(", "), "", "Try using a different name like:", `• ${reservedName}-app`, `• ${reservedName}-service`, `• web-${reservedName}`, `• api-${reservedName}`, `• my-${reservedName}`, "", "Example fix:", "# Before:", `services:`, ` ${reservedName}:`, " # ... your config", "", "# After:", `services:`, ` ${reservedName}-app:`, " # ... your config", ]; return suggestions; } /** * Generates suggestions for resolving port conflicts */ generatePortConflictSuggestions(port, conflictingEntries, serverName) { const suggestions = [ `Port ${port} conflict between: ${conflictingEntries.join(", ")}`, "", "Solutions:", `1. Use different host ports (e.g., ${port + 1000}, ${port + 2000})`, "2. Remove port mappings if external access is not needed", "3. Deploy conflicting services to different servers", "", "Example fix:", `# Original (conflicting):`, `services:`, ` ${conflictingEntries[0]}:`, ` ports: ["${port}:${port}"]`, ` ${conflictingEntries[1]}:`, ` ports: ["${port}:${port}"]`, "", "# Fixed:", `services:`, ` ${conflictingEntries[0]}:`, ` ports: ["${port}:${port}"]`, ` ${conflictingEntries[1]}:`, ` ports: ["${port + 1000}:${port}"] # Different host port`, "", "Or remove external access (recommended for databases):", `services:`, ` ${conflictingEntries[0]}:`, ` # ports: [...] # Remove for internal-only access`, ` ${conflictingEntries[1]}:`, ` # ports: [...] # Remove for internal-only access`, ]; return suggestions; } } exports.ConfigValidator = ConfigValidator; // Reserved command names that cannot be used as app/service names ConfigValidator.RESERVED_NAMES = ["init", "status", "proxy"]; /** * Validates a iop configuration and returns any errors found */ function validateConfig(config) { const validator = new ConfigValidator(config); return validator.validate(); } /** * Formats validation errors for display */ function formatValidationErrors(errors) { const formatted = []; if (errors.length === 0) { return formatted; } formatted.push("✗ Configuration validation failed:"); formatted.push(""); for (const error of errors) { formatted.push(`• ${error.message}`); if (error.suggestions) { formatted.push(""); for (const suggestion of error.suggestions) { formatted.push(` ${suggestion}`); } } formatted.push(""); } return formatted; } //# sourceMappingURL=config-validator.js.map