suthep 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +44 -1
  2. package/dist/commands/deploy.js +4 -5
  3. package/dist/commands/deploy.js.map +1 -1
  4. package/dist/commands/down.js +5 -6
  5. package/dist/commands/down.js.map +1 -1
  6. package/dist/commands/list.js +168 -0
  7. package/dist/commands/list.js.map +1 -0
  8. package/dist/commands/logs.js +155 -0
  9. package/dist/commands/logs.js.map +1 -0
  10. package/dist/commands/restart.js +232 -0
  11. package/dist/commands/restart.js.map +1 -0
  12. package/dist/commands/self-update.js +119 -0
  13. package/dist/commands/self-update.js.map +1 -0
  14. package/dist/commands/up.js +5 -6
  15. package/dist/commands/up.js.map +1 -1
  16. package/dist/index.js +34 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/utils/docker.js +63 -0
  19. package/dist/utils/docker.js.map +1 -1
  20. package/dist/utils/service-finder.js +27 -0
  21. package/dist/utils/service-finder.js.map +1 -0
  22. package/docs/english/03-quick-start.md +16 -0
  23. package/docs/english/05-commands.md +276 -6
  24. package/docs/thai/03-quick-start.md +16 -0
  25. package/docs/thai/05-commands.md +273 -6
  26. package/package.json +5 -1
  27. package/src/commands/__tests__/logs.test.ts +466 -0
  28. package/src/commands/__tests__/restart.test.ts +857 -0
  29. package/src/commands/deploy.ts +7 -9
  30. package/src/commands/down.ts +8 -10
  31. package/src/commands/list.ts +224 -0
  32. package/src/commands/logs.ts +197 -0
  33. package/src/commands/restart.ts +300 -0
  34. package/src/commands/self-update.ts +144 -0
  35. package/src/commands/up.ts +8 -10
  36. package/src/index.ts +63 -3
  37. package/src/utils/__tests__/service-finder.test.ts +263 -0
  38. package/src/utils/docker.ts +79 -0
  39. package/src/utils/service-finder.ts +54 -0
  40. package/.scannerwork/.sonar_lock +0 -0
  41. package/.scannerwork/report-task.txt +0 -6
  42. package/example/suthep-complete.yml +0 -103
  43. package/example/suthep-docker-only.yml +0 -71
  44. package/example/suthep-env-example.yml +0 -113
  45. package/example/suthep-no-docker.yml +0 -51
  46. package/example/suthep-path-routing.yml +0 -62
  47. package/example/suthep.example.yml +0 -88
  48. package/suthep-1.0.0.tgz +0 -0
  49. package/suthep.yml +0 -39
  50. package/todo.md +0 -6
package/README.md CHANGED
@@ -185,12 +185,55 @@ suthep deploy [service-name] [-f suthep.yml] [--no-https] [--no-nginx] [-e KEY=V
185
185
  ```
186
186
 
187
187
  **Options:**
188
- - `service-name`: Name of the service to deploy (optional, deploys all services if not specified)
188
+ - `service-name`: Name or index (1-based) of the service to deploy (optional, deploys all services if not specified). Use `suthep list` to see available services with indices.
189
189
  - `-f, --file`: Configuration file path (default: `suthep.yml`)
190
190
  - `--no-https`: Skip HTTPS/SSL certificate setup
191
191
  - `--no-nginx`: Skip Nginx configuration (useful for testing)
192
192
  - `-e, --env`: Set environment variables (can be used multiple times, e.g., `-e KEY1=value1 -e KEY2=value2`)
193
193
 
194
+ ### `suthep restart`
195
+
196
+ Restart services (stop and start containers, update Nginx configs).
197
+
198
+ ```bash
199
+ suthep restart [service-name] [-f suthep.yml] [--all] [--no-https] [--no-nginx]
200
+ ```
201
+
202
+ **Options:**
203
+ - `service-name`: Name or index (1-based) of the service to restart (optional). Use `suthep list` to see available services with indices.
204
+ - `-f, --file`: Configuration file path (default: `suthep.yml`)
205
+ - `--all`: Restart all services
206
+ - `--no-https`: Skip HTTPS setup
207
+ - `--no-nginx`: Skip Nginx configuration
208
+
209
+ ### `suthep list`
210
+
211
+ List all services and their status (running, stopped, container status, Nginx configuration).
212
+
213
+ ```bash
214
+ suthep list [-f suthep.yml]
215
+ # or
216
+ suthep ls [-f suthep.yml]
217
+ ```
218
+
219
+ **Options:**
220
+ - `-f, --file`: Configuration file path (default: `suthep.yml`)
221
+
222
+ **Options:**
223
+ - `-f, --file`: Configuration file path (default: `suthep.yml`)
224
+
225
+ **Output:**
226
+ - Displays a formatted table with service index numbers, status, ports, container status, and Nginx configuration
227
+ - Shows summary statistics (running, stopped, total)
228
+ - Color-coded status indicators (green for running, red for stopped, yellow for partial)
229
+
230
+ **Service Selection:**
231
+ All service commands (`deploy`, `up`, `down`, `restart`, `logs`) support selecting services by:
232
+ - **Name**: Use the service name (e.g., `suthep restart api`)
233
+ - **Index**: Use the 1-based index number shown in `suthep list` (e.g., `suthep restart 1`)
234
+
235
+ Use `suthep list` to see all services with their index numbers.
236
+
194
237
  ## Examples
195
238
 
196
239
  ### Example 1: Simple Node.js API Service
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import fs from "fs-extra";
3
3
  import { certificateExists, requestCertificate } from "../utils/certbot.js";
4
4
  import { loadConfig } from "../utils/config-loader.js";
5
+ import { findServiceByIdentifier, getServiceNotFoundError } from "../utils/service-finder.js";
5
6
  import { deployService, performHealthCheck } from "../utils/deployment.js";
6
7
  import { startDockerContainerZeroDowntime, startDockerContainer, swapContainersForZeroDowntime, cleanupTempContainer } from "../utils/docker.js";
7
8
  import { getCanonicalDomain, generateNginxConfig, generateMultiServiceNginxConfig, writeNginxConfig, enableSite, reloadNginx } from "../utils/nginx.js";
@@ -15,15 +16,13 @@ async function deployCommand(options) {
15
16
  const config = await loadConfig(options.file);
16
17
  let servicesToDeploy = [];
17
18
  if (options.serviceName) {
18
- const service = config.services.find((s) => s.name === options.serviceName);
19
+ const service = findServiceByIdentifier(config, options.serviceName);
19
20
  if (!service) {
20
- throw new Error(
21
- `Service "${options.serviceName}" not found in configuration. Available services: ${config.services.map((s) => s.name).join(", ")}`
22
- );
21
+ throw new Error(getServiceNotFoundError(options.serviceName, config));
23
22
  }
24
23
  servicesToDeploy = [service];
25
24
  console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`));
26
- console.log(chalk.cyan(`📋 Deploying service: ${options.serviceName}
25
+ console.log(chalk.cyan(`📋 Deploying service: ${service.name}
27
26
  `));
28
27
  } else {
29
28
  servicesToDeploy = config.services;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.js","sources":["../../src/commands/deploy.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { certificateExists, requestCertificate } from '../utils/certbot'\nimport { loadConfig } from '../utils/config-loader'\nimport { deployService, performHealthCheck } from '../utils/deployment'\nimport {\n cleanupTempContainer,\n startDockerContainer,\n startDockerContainerZeroDowntime,\n swapContainersForZeroDowntime,\n type ZeroDowntimeContainerInfo,\n} from '../utils/docker'\nimport {\n enableSite,\n generateMultiServiceNginxConfig,\n generateNginxConfig,\n getCanonicalDomain,\n reloadNginx,\n writeNginxConfig,\n} from '../utils/nginx'\n\ninterface DeployOptions {\n file: string\n https: boolean\n nginx: boolean\n serviceName?: string\n cliEnvVars?: Record<string, string>\n}\n\nexport async function deployCommand(options: DeployOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🚀 Deploying Services\\n'))\n\n try {\n // Load configuration (this will also load .env files for variable substitution)\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n\n // Filter services based on serviceName if provided\n let servicesToDeploy: ServiceConfig[] = []\n if (options.serviceName) {\n const service = config.services.find((s) => s.name === options.serviceName)\n if (!service) {\n throw new Error(\n `Service \"${\n options.serviceName\n }\" not found in configuration. Available services: ${config.services\n .map((s) => s.name)\n .join(', ')}`\n )\n }\n servicesToDeploy = [service]\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n console.log(chalk.cyan(`📋 Deploying service: ${options.serviceName}\\n`))\n } else {\n servicesToDeploy = config.services\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n console.log(\n chalk.cyan(`📋 Deploying all services: ${servicesToDeploy.map((s) => s.name).join(', ')}\\n`)\n )\n }\n\n // Group services by domain\n // When deploying a single service, include ALL services from config that share the same domain(s)\n // This ensures nginx config includes both old and new services\n const domainToServices = new Map<string, ServiceConfig[]>()\n const allDomains = new Set<string>()\n\n // Collect all domains from services being deployed\n for (const service of servicesToDeploy) {\n for (const domain of service.domains) {\n allDomains.add(domain)\n }\n }\n\n // If deploying a single service, include all services that share the same domain(s)\n // Otherwise, use only services being deployed\n const servicesForNginx = options.serviceName\n ? config.services.filter((service) => {\n // Include service if it shares any domain with services being deployed\n return service.domains.some((domain) => allDomains.has(domain))\n })\n : servicesToDeploy\n\n // Log when additional services are included in nginx config\n if (options.serviceName && servicesForNginx.length > servicesToDeploy.length) {\n const additionalServices = servicesForNginx.filter(\n (s) => !servicesToDeploy.some((d) => d.name === s.name)\n )\n console.log(\n chalk.cyan(\n ` 📋 Including ${\n additionalServices.length\n } additional service(s) in nginx config: ${additionalServices\n .map((s) => s.name)\n .join(', ')}`\n )\n )\n }\n\n // Group services by canonical domain for nginx configuration\n // This ensures www and non-www root domains use the same config file\n const canonicalDomains = new Set<string>()\n for (const service of servicesForNginx) {\n for (const domain of service.domains) {\n if (allDomains.has(domain)) {\n // Get canonical domain (www if both exist, otherwise the domain itself)\n const canonicalDomain = getCanonicalDomain(domain, allDomains)\n canonicalDomains.add(canonicalDomain)\n\n // Group services by canonical domain\n if (!domainToServices.has(canonicalDomain)) {\n domainToServices.set(canonicalDomain, [])\n }\n // Only add service if not already added for this canonical domain\n if (!domainToServices.get(canonicalDomain)!.some((s) => s.name === service.name)) {\n domainToServices.get(canonicalDomain)!.push(service)\n }\n }\n }\n }\n\n // Deploy each service (Docker, health checks, etc.)\n // Track zero-downtime info for services that need it\n const serviceTempInfo = new Map<string, ZeroDowntimeContainerInfo | null>()\n\n for (const service of servicesToDeploy) {\n console.log(chalk.cyan(`\\n📦 Deploying service: ${service.name}`))\n\n try {\n // Start Docker container if configured\n if (service.docker) {\n console.log(chalk.dim(' 🐳 Managing Docker container...'))\n\n // Use zero-downtime deployment if strategy is blue-green or rolling\n if (\n config.deployment.strategy === 'blue-green' ||\n config.deployment.strategy === 'rolling'\n ) {\n const tempInfo = await startDockerContainerZeroDowntime(service, options.cliEnvVars)\n serviceTempInfo.set(service.name, tempInfo)\n\n if (tempInfo && tempInfo.oldContainerExists) {\n console.log(\n chalk.cyan(\n ` 🔄 Zero-downtime deployment: new container on port ${tempInfo.tempPort}`\n )\n )\n }\n } else {\n // Fallback to regular deployment\n await startDockerContainer(service, options.cliEnvVars)\n serviceTempInfo.set(service.name, null)\n }\n } else {\n serviceTempInfo.set(service.name, null)\n }\n\n // Deploy the service (with temp info for zero-downtime)\n const tempInfo = serviceTempInfo.get(service.name) || null\n await deployService(service, config.deployment, tempInfo)\n\n // Perform health check on appropriate port\n if (service.healthCheck) {\n console.log(chalk.dim(` 🏥 Performing health check...`))\n const checkPort =\n tempInfo && tempInfo.oldContainerExists ? tempInfo.tempPort : service.port\n const isHealthy = await performHealthCheck(\n `http://localhost:${checkPort}${service.healthCheck.path}`,\n config.deployment.healthCheckTimeout\n )\n\n if (isHealthy) {\n console.log(chalk.green(` ✅ Service ${service.name} is healthy`))\n } else {\n throw new Error(`Health check failed for service ${service.name}`)\n }\n }\n\n console.log(chalk.green.bold(`✨ Service ${service.name} deployed successfully!`))\n } catch (error) {\n console.error(\n chalk.red(`\\n❌ Failed to deploy service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Helper function to generate nginx configs with optional port overrides\n const generateNginxConfigsForDomain = (\n domain: string,\n withHttps: boolean,\n portOverrides?: Map<string, number>\n ): string => {\n const servicesForDomain = domainToServices.get(domain)!\n if (servicesForDomain.length === 1) {\n const service = servicesForDomain[0]\n const portOverride = portOverrides?.get(service.name)\n return generateNginxConfig(service, withHttps, portOverride)\n } else {\n return generateMultiServiceNginxConfig(servicesForDomain, domain, withHttps, portOverrides)\n }\n }\n\n // Check if we need zero-downtime nginx updates (any service has temp container)\n const needsZeroDowntimeNginx = Array.from(serviceTempInfo.values()).some(\n (info) => info !== null && info.oldContainerExists\n )\n\n // Configure Nginx per domain\n if (options.nginx) {\n // If zero-downtime, first update nginx to point to temp ports\n if (needsZeroDowntimeNginx) {\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx for zero-downtime deployment...`))\n\n // Build port override map for temp ports\n const tempPortOverrides = new Map<string, number>()\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists) {\n tempPortOverrides.set(service.name, tempInfo.tempPort)\n }\n }\n\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(\n canonicalDomain,\n false,\n tempPortOverrides\n )\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Nginx updated for ${canonicalDomain} (temporary ports)`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload nginx to switch to temp ports (graceful reload, no connection drops)\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx to switch to new containers...`))\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded, traffic now routed to new containers`))\n\n // Now swap containers (stop old, promote new)\n console.log(chalk.cyan(`\\n🔄 Swapping containers for zero-downtime...`))\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists && service.docker) {\n try {\n await swapContainersForZeroDowntime(service, tempInfo, options.cliEnvVars)\n console.log(chalk.green(` ✅ Container swapped for ${service.name}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to swap container for ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n\n // Update nginx back to original ports (before stopping temp containers)\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx back to production ports...`))\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, false)\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Nginx updated for ${canonicalDomain} (production ports)`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload nginx to switch to production ports (graceful reload)\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx to switch to production ports...`))\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded, traffic now routed to production containers`))\n\n // Clean up temp containers (nginx already pointing to production, so safe to remove)\n console.log(chalk.cyan(`\\n🧹 Cleaning up temporary containers...`))\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists) {\n await cleanupTempContainer(tempInfo.tempContainerName)\n }\n }\n } else {\n // Regular nginx configuration (no zero-downtime needed)\n console.log(chalk.cyan(`\\n⚙️ Configuring Nginx reverse proxy...`))\n\n for (const canonicalDomain of canonicalDomains) {\n const servicesForDomain = domainToServices.get(canonicalDomain)!\n const configName = canonicalDomain.replace(/\\./g, '_')\n\n try {\n // Log domain and services configuration\n if (servicesForDomain.length > 1) {\n console.log(\n chalk.cyan(\n ` 📋 Configuring ${canonicalDomain} with ${\n servicesForDomain.length\n } services: ${servicesForDomain.map((s) => s.name).join(', ')}`\n )\n )\n console.log(\n chalk.dim(\n ` All services will share the same nginx config file: ${configName}.conf`\n )\n )\n } else {\n // Single service, but log it anyway for clarity\n console.log(\n chalk.cyan(\n ` 📋 Configuring ${canonicalDomain} with service: ${servicesForDomain[0].name}`\n )\n )\n }\n\n // Generate Nginx config\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, false)\n\n // Check if config file already exists and write/override it\n const wasOverridden = await writeNginxConfig(\n configName,\n config.nginx.configPath,\n nginxConfigContent\n )\n\n if (wasOverridden) {\n console.log(\n chalk.yellow(\n ` 🔄 Nginx config \"${configName}.conf\" already exists, deleting and recreating with new configuration...`\n )\n )\n }\n\n await enableSite(configName, config.nginx.configPath)\n\n console.log(chalk.green(` ✅ Nginx configured for ${canonicalDomain}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to configure Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n }\n\n // Setup HTTPS with Certbot (per canonical domain, not per service)\n if (options.https && canonicalDomains.size > 0) {\n console.log(chalk.cyan(`\\n🔐 Setting up HTTPS certificates...`))\n\n for (const canonicalDomain of canonicalDomains) {\n try {\n // Check if certificate already exists\n const exists = await certificateExists(canonicalDomain)\n if (exists) {\n console.log(\n chalk.green(\n ` ✅ SSL certificate already exists for ${canonicalDomain}, skipping certificate creation`\n )\n )\n console.log(\n chalk.dim(\n ` Using existing certificate from /etc/letsencrypt/live/${canonicalDomain}/`\n )\n )\n } else {\n // Request new certificate\n console.log(chalk.cyan(` 📜 Requesting SSL certificate for ${canonicalDomain}...`))\n try {\n await requestCertificate(\n canonicalDomain,\n config.certbot.email,\n config.certbot.staging\n )\n console.log(chalk.green(` ✅ SSL certificate obtained for ${canonicalDomain}`))\n } catch (error: any) {\n // Check if error is because certificate already exists (race condition or check missed it)\n const errorMessage = error?.message || String(error) || ''\n if (\n errorMessage.includes('already exists') ||\n errorMessage.includes('Skipping certificate creation')\n ) {\n console.log(\n chalk.green(\n ` ✅ SSL certificate already exists for ${canonicalDomain} (detected during request), skipping...`\n )\n )\n } else {\n throw error // Re-throw if it's a different error\n }\n }\n }\n } catch (error) {\n console.log(\n chalk.yellow(\n ` ⚠️ Failed to obtain SSL for ${canonicalDomain}: ${\n error instanceof Error ? error.message : error\n }`\n )\n )\n }\n }\n\n // Update Nginx configs with HTTPS\n if (options.nginx) {\n console.log(chalk.cyan(`\\n🔄 Updating Nginx configs with HTTPS...`))\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, true)\n const wasOverridden = await writeNginxConfig(\n configName,\n config.nginx.configPath,\n nginxConfigContent\n )\n\n if (wasOverridden) {\n console.log(\n chalk.yellow(\n ` 🔄 Nginx config \"${configName}.conf\" already exists, deleting and recreating with new HTTPS configuration...`\n )\n )\n }\n console.log(chalk.green(` ✅ HTTPS config updated for ${canonicalDomain}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update HTTPS config for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n }\n\n // Final reload Nginx after all configurations (only if we didn't already reload for zero-downtime)\n if (options.nginx && !needsZeroDowntimeNginx) {\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx...`))\n await reloadNginx(config.nginx.reloadCommand)\n } else if (options.nginx && needsZeroDowntimeNginx) {\n // Final reload after HTTPS update\n console.log(chalk.cyan(`\\n🔄 Final Nginx reload with HTTPS...`))\n await reloadNginx(config.nginx.reloadCommand)\n }\n\n console.log(chalk.green.bold('\\n🎉 All services deployed successfully!\\n'))\n\n // Print service URLs\n console.log(chalk.cyan('📋 Service URLs:'))\n for (const service of servicesToDeploy) {\n for (const domain of service.domains) {\n const protocol = options.https ? 'https' : 'http'\n const servicePath = service.path || '/'\n const fullPath = servicePath === '/' ? '' : servicePath\n console.log(chalk.dim(` ${service.name}: ${protocol}://${domain}${fullPath}`))\n }\n }\n console.log()\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Deployment failed:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":["tempInfo"],"mappings":"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"}
1
+ {"version":3,"file":"deploy.js","sources":["../../src/commands/deploy.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { certificateExists, requestCertificate } from '../utils/certbot'\nimport { loadConfig } from '../utils/config-loader'\nimport {\n findServiceByIdentifier,\n getServiceNotFoundError,\n} from '../utils/service-finder'\nimport { deployService, performHealthCheck } from '../utils/deployment'\nimport {\n cleanupTempContainer,\n startDockerContainer,\n startDockerContainerZeroDowntime,\n swapContainersForZeroDowntime,\n type ZeroDowntimeContainerInfo,\n} from '../utils/docker'\nimport {\n enableSite,\n generateMultiServiceNginxConfig,\n generateNginxConfig,\n getCanonicalDomain,\n reloadNginx,\n writeNginxConfig,\n} from '../utils/nginx'\n\ninterface DeployOptions {\n file: string\n https: boolean\n nginx: boolean\n serviceName?: string\n cliEnvVars?: Record<string, string>\n}\n\nexport async function deployCommand(options: DeployOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🚀 Deploying Services\\n'))\n\n try {\n // Load configuration (this will also load .env files for variable substitution)\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n\n // Filter services based on serviceName if provided\n let servicesToDeploy: ServiceConfig[] = []\n if (options.serviceName) {\n const service = findServiceByIdentifier(config, options.serviceName)\n if (!service) {\n throw new Error(getServiceNotFoundError(options.serviceName, config))\n }\n servicesToDeploy = [service]\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n console.log(chalk.cyan(`📋 Deploying service: ${service.name}\\n`))\n } else {\n servicesToDeploy = config.services\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n console.log(\n chalk.cyan(`📋 Deploying all services: ${servicesToDeploy.map((s) => s.name).join(', ')}\\n`)\n )\n }\n\n // Group services by domain\n // When deploying a single service, include ALL services from config that share the same domain(s)\n // This ensures nginx config includes both old and new services\n const domainToServices = new Map<string, ServiceConfig[]>()\n const allDomains = new Set<string>()\n\n // Collect all domains from services being deployed\n for (const service of servicesToDeploy) {\n for (const domain of service.domains) {\n allDomains.add(domain)\n }\n }\n\n // If deploying a single service, include all services that share the same domain(s)\n // Otherwise, use only services being deployed\n const servicesForNginx = options.serviceName\n ? config.services.filter((service) => {\n // Include service if it shares any domain with services being deployed\n return service.domains.some((domain) => allDomains.has(domain))\n })\n : servicesToDeploy\n\n // Log when additional services are included in nginx config\n if (options.serviceName && servicesForNginx.length > servicesToDeploy.length) {\n const additionalServices = servicesForNginx.filter(\n (s) => !servicesToDeploy.some((d) => d.name === s.name)\n )\n console.log(\n chalk.cyan(\n ` 📋 Including ${\n additionalServices.length\n } additional service(s) in nginx config: ${additionalServices\n .map((s) => s.name)\n .join(', ')}`\n )\n )\n }\n\n // Group services by canonical domain for nginx configuration\n // This ensures www and non-www root domains use the same config file\n const canonicalDomains = new Set<string>()\n for (const service of servicesForNginx) {\n for (const domain of service.domains) {\n if (allDomains.has(domain)) {\n // Get canonical domain (www if both exist, otherwise the domain itself)\n const canonicalDomain = getCanonicalDomain(domain, allDomains)\n canonicalDomains.add(canonicalDomain)\n\n // Group services by canonical domain\n if (!domainToServices.has(canonicalDomain)) {\n domainToServices.set(canonicalDomain, [])\n }\n // Only add service if not already added for this canonical domain\n if (!domainToServices.get(canonicalDomain)!.some((s) => s.name === service.name)) {\n domainToServices.get(canonicalDomain)!.push(service)\n }\n }\n }\n }\n\n // Deploy each service (Docker, health checks, etc.)\n // Track zero-downtime info for services that need it\n const serviceTempInfo = new Map<string, ZeroDowntimeContainerInfo | null>()\n\n for (const service of servicesToDeploy) {\n console.log(chalk.cyan(`\\n📦 Deploying service: ${service.name}`))\n\n try {\n // Start Docker container if configured\n if (service.docker) {\n console.log(chalk.dim(' 🐳 Managing Docker container...'))\n\n // Use zero-downtime deployment if strategy is blue-green or rolling\n if (\n config.deployment.strategy === 'blue-green' ||\n config.deployment.strategy === 'rolling'\n ) {\n const tempInfo = await startDockerContainerZeroDowntime(service, options.cliEnvVars)\n serviceTempInfo.set(service.name, tempInfo)\n\n if (tempInfo && tempInfo.oldContainerExists) {\n console.log(\n chalk.cyan(\n ` 🔄 Zero-downtime deployment: new container on port ${tempInfo.tempPort}`\n )\n )\n }\n } else {\n // Fallback to regular deployment\n await startDockerContainer(service, options.cliEnvVars)\n serviceTempInfo.set(service.name, null)\n }\n } else {\n serviceTempInfo.set(service.name, null)\n }\n\n // Deploy the service (with temp info for zero-downtime)\n const tempInfo = serviceTempInfo.get(service.name) || null\n await deployService(service, config.deployment, tempInfo)\n\n // Perform health check on appropriate port\n if (service.healthCheck) {\n console.log(chalk.dim(` 🏥 Performing health check...`))\n const checkPort =\n tempInfo && tempInfo.oldContainerExists ? tempInfo.tempPort : service.port\n const isHealthy = await performHealthCheck(\n `http://localhost:${checkPort}${service.healthCheck.path}`,\n config.deployment.healthCheckTimeout\n )\n\n if (isHealthy) {\n console.log(chalk.green(` ✅ Service ${service.name} is healthy`))\n } else {\n throw new Error(`Health check failed for service ${service.name}`)\n }\n }\n\n console.log(chalk.green.bold(`✨ Service ${service.name} deployed successfully!`))\n } catch (error) {\n console.error(\n chalk.red(`\\n❌ Failed to deploy service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Helper function to generate nginx configs with optional port overrides\n const generateNginxConfigsForDomain = (\n domain: string,\n withHttps: boolean,\n portOverrides?: Map<string, number>\n ): string => {\n const servicesForDomain = domainToServices.get(domain)!\n if (servicesForDomain.length === 1) {\n const service = servicesForDomain[0]\n const portOverride = portOverrides?.get(service.name)\n return generateNginxConfig(service, withHttps, portOverride)\n } else {\n return generateMultiServiceNginxConfig(servicesForDomain, domain, withHttps, portOverrides)\n }\n }\n\n // Check if we need zero-downtime nginx updates (any service has temp container)\n const needsZeroDowntimeNginx = Array.from(serviceTempInfo.values()).some(\n (info) => info !== null && info.oldContainerExists\n )\n\n // Configure Nginx per domain\n if (options.nginx) {\n // If zero-downtime, first update nginx to point to temp ports\n if (needsZeroDowntimeNginx) {\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx for zero-downtime deployment...`))\n\n // Build port override map for temp ports\n const tempPortOverrides = new Map<string, number>()\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists) {\n tempPortOverrides.set(service.name, tempInfo.tempPort)\n }\n }\n\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(\n canonicalDomain,\n false,\n tempPortOverrides\n )\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Nginx updated for ${canonicalDomain} (temporary ports)`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload nginx to switch to temp ports (graceful reload, no connection drops)\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx to switch to new containers...`))\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded, traffic now routed to new containers`))\n\n // Now swap containers (stop old, promote new)\n console.log(chalk.cyan(`\\n🔄 Swapping containers for zero-downtime...`))\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists && service.docker) {\n try {\n await swapContainersForZeroDowntime(service, tempInfo, options.cliEnvVars)\n console.log(chalk.green(` ✅ Container swapped for ${service.name}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to swap container for ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n\n // Update nginx back to original ports (before stopping temp containers)\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx back to production ports...`))\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, false)\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Nginx updated for ${canonicalDomain} (production ports)`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload nginx to switch to production ports (graceful reload)\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx to switch to production ports...`))\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded, traffic now routed to production containers`))\n\n // Clean up temp containers (nginx already pointing to production, so safe to remove)\n console.log(chalk.cyan(`\\n🧹 Cleaning up temporary containers...`))\n for (const service of servicesToDeploy) {\n const tempInfo = serviceTempInfo.get(service.name)\n if (tempInfo && tempInfo.oldContainerExists) {\n await cleanupTempContainer(tempInfo.tempContainerName)\n }\n }\n } else {\n // Regular nginx configuration (no zero-downtime needed)\n console.log(chalk.cyan(`\\n⚙️ Configuring Nginx reverse proxy...`))\n\n for (const canonicalDomain of canonicalDomains) {\n const servicesForDomain = domainToServices.get(canonicalDomain)!\n const configName = canonicalDomain.replace(/\\./g, '_')\n\n try {\n // Log domain and services configuration\n if (servicesForDomain.length > 1) {\n console.log(\n chalk.cyan(\n ` 📋 Configuring ${canonicalDomain} with ${\n servicesForDomain.length\n } services: ${servicesForDomain.map((s) => s.name).join(', ')}`\n )\n )\n console.log(\n chalk.dim(\n ` All services will share the same nginx config file: ${configName}.conf`\n )\n )\n } else {\n // Single service, but log it anyway for clarity\n console.log(\n chalk.cyan(\n ` 📋 Configuring ${canonicalDomain} with service: ${servicesForDomain[0].name}`\n )\n )\n }\n\n // Generate Nginx config\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, false)\n\n // Check if config file already exists and write/override it\n const wasOverridden = await writeNginxConfig(\n configName,\n config.nginx.configPath,\n nginxConfigContent\n )\n\n if (wasOverridden) {\n console.log(\n chalk.yellow(\n ` 🔄 Nginx config \"${configName}.conf\" already exists, deleting and recreating with new configuration...`\n )\n )\n }\n\n await enableSite(configName, config.nginx.configPath)\n\n console.log(chalk.green(` ✅ Nginx configured for ${canonicalDomain}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to configure Nginx for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n }\n\n // Setup HTTPS with Certbot (per canonical domain, not per service)\n if (options.https && canonicalDomains.size > 0) {\n console.log(chalk.cyan(`\\n🔐 Setting up HTTPS certificates...`))\n\n for (const canonicalDomain of canonicalDomains) {\n try {\n // Check if certificate already exists\n const exists = await certificateExists(canonicalDomain)\n if (exists) {\n console.log(\n chalk.green(\n ` ✅ SSL certificate already exists for ${canonicalDomain}, skipping certificate creation`\n )\n )\n console.log(\n chalk.dim(\n ` Using existing certificate from /etc/letsencrypt/live/${canonicalDomain}/`\n )\n )\n } else {\n // Request new certificate\n console.log(chalk.cyan(` 📜 Requesting SSL certificate for ${canonicalDomain}...`))\n try {\n await requestCertificate(\n canonicalDomain,\n config.certbot.email,\n config.certbot.staging\n )\n console.log(chalk.green(` ✅ SSL certificate obtained for ${canonicalDomain}`))\n } catch (error: any) {\n // Check if error is because certificate already exists (race condition or check missed it)\n const errorMessage = error?.message || String(error) || ''\n if (\n errorMessage.includes('already exists') ||\n errorMessage.includes('Skipping certificate creation')\n ) {\n console.log(\n chalk.green(\n ` ✅ SSL certificate already exists for ${canonicalDomain} (detected during request), skipping...`\n )\n )\n } else {\n throw error // Re-throw if it's a different error\n }\n }\n }\n } catch (error) {\n console.log(\n chalk.yellow(\n ` ⚠️ Failed to obtain SSL for ${canonicalDomain}: ${\n error instanceof Error ? error.message : error\n }`\n )\n )\n }\n }\n\n // Update Nginx configs with HTTPS\n if (options.nginx) {\n console.log(chalk.cyan(`\\n🔄 Updating Nginx configs with HTTPS...`))\n for (const canonicalDomain of canonicalDomains) {\n const configName = canonicalDomain.replace(/\\./g, '_')\n\n try {\n const nginxConfigContent = generateNginxConfigsForDomain(canonicalDomain, true)\n const wasOverridden = await writeNginxConfig(\n configName,\n config.nginx.configPath,\n nginxConfigContent\n )\n\n if (wasOverridden) {\n console.log(\n chalk.yellow(\n ` 🔄 Nginx config \"${configName}.conf\" already exists, deleting and recreating with new HTTPS configuration...`\n )\n )\n }\n console.log(chalk.green(` ✅ HTTPS config updated for ${canonicalDomain}`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update HTTPS config for ${canonicalDomain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n }\n\n // Final reload Nginx after all configurations (only if we didn't already reload for zero-downtime)\n if (options.nginx && !needsZeroDowntimeNginx) {\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx...`))\n await reloadNginx(config.nginx.reloadCommand)\n } else if (options.nginx && needsZeroDowntimeNginx) {\n // Final reload after HTTPS update\n console.log(chalk.cyan(`\\n🔄 Final Nginx reload with HTTPS...`))\n await reloadNginx(config.nginx.reloadCommand)\n }\n\n console.log(chalk.green.bold('\\n🎉 All services deployed successfully!\\n'))\n\n // Print service URLs\n console.log(chalk.cyan('📋 Service URLs:'))\n for (const service of servicesToDeploy) {\n for (const domain of service.domains) {\n const protocol = options.https ? 'https' : 'http'\n const servicePath = service.path || '/'\n const fullPath = servicePath === '/' ? '' : servicePath\n console.log(chalk.dim(` ${service.name}: ${protocol}://${domain}${fullPath}`))\n }\n }\n console.log()\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Deployment failed:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":["tempInfo"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import fs from "fs-extra";
3
3
  import { loadConfig } from "../utils/config-loader.js";
4
+ import { findServiceByIdentifier, getServiceNotFoundError } from "../utils/service-finder.js";
4
5
  import { stopDockerContainer, removeDockerContainer, isContainerRunning } from "../utils/docker.js";
5
6
  import { disableSite, generateNginxConfig, generateMultiServiceNginxConfig, writeNginxConfig, enableSite, reloadNginx } from "../utils/nginx.js";
6
7
  async function downCommand(options) {
@@ -22,17 +23,15 @@ async function downCommand(options) {
22
23
  )
23
24
  );
24
25
  } else if (options.serviceName) {
25
- const service = config.services.find((s) => s.name === options.serviceName);
26
+ const service = findServiceByIdentifier(config, options.serviceName);
26
27
  if (!service) {
27
- throw new Error(
28
- `Service "${options.serviceName}" not found in configuration. Available services: ${config.services.map((s) => s.name).join(", ")}`
29
- );
28
+ throw new Error(getServiceNotFoundError(options.serviceName, config));
30
29
  }
31
30
  servicesToDown = [service];
32
- console.log(chalk.cyan(`📋 Bringing down service: ${options.serviceName}
31
+ console.log(chalk.cyan(`📋 Bringing down service: ${service.name}
33
32
  `));
34
33
  } else {
35
- throw new Error("Either specify a service name or use --all flag");
34
+ throw new Error("Either specify a service name/index or use --all flag");
36
35
  }
37
36
  const domainToServices = /* @__PURE__ */ new Map();
38
37
  const allDomains = /* @__PURE__ */ new Set();
@@ -1 +1 @@
1
- {"version":3,"file":"down.js","sources":["../../src/commands/down.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { loadConfig } from '../utils/config-loader'\nimport { isContainerRunning, removeDockerContainer, stopDockerContainer } from '../utils/docker'\nimport {\n disableSite,\n enableSite,\n generateMultiServiceNginxConfig,\n generateNginxConfig,\n reloadNginx,\n writeNginxConfig,\n} from '../utils/nginx'\n\ninterface DownOptions {\n file: string\n all: boolean\n serviceName?: string\n}\n\nexport async function downCommand(options: DownOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🛑 Bringing Down Services\\n'))\n\n try {\n // Load configuration\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n\n // Determine which services to bring down\n let servicesToDown: ServiceConfig[] = []\n\n if (options.all) {\n servicesToDown = config.services\n console.log(\n chalk.cyan(\n `📋 Bringing down all services: ${servicesToDown.map((s) => s.name).join(', ')}\\n`\n )\n )\n } else if (options.serviceName) {\n const service = config.services.find((s) => s.name === options.serviceName)\n if (!service) {\n throw new Error(\n `Service \"${\n options.serviceName\n }\" not found in configuration. Available services: ${config.services\n .map((s) => s.name)\n .join(', ')}`\n )\n }\n servicesToDown = [service]\n console.log(chalk.cyan(`📋 Bringing down service: ${options.serviceName}\\n`))\n } else {\n throw new Error('Either specify a service name or use --all flag')\n }\n\n // Group services by domain for nginx config management\n const domainToServices = new Map<string, ServiceConfig[]>()\n const allDomains = new Set<string>()\n\n for (const service of servicesToDown) {\n for (const domain of service.domains) {\n allDomains.add(domain)\n if (!domainToServices.has(domain)) {\n domainToServices.set(domain, [])\n }\n domainToServices.get(domain)!.push(service)\n }\n }\n\n // Stop and remove Docker containers\n for (const service of servicesToDown) {\n if (service.docker) {\n console.log(chalk.cyan(`\\n🐳 Stopping Docker container for service: ${service.name}`))\n try {\n const containerName = service.docker.container\n\n // Stop container\n try {\n await stopDockerContainer(containerName)\n console.log(chalk.green(` ✅ Stopped container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already stopped)`)\n )\n } else {\n throw error\n }\n }\n\n // Remove container\n try {\n await removeDockerContainer(containerName)\n console.log(chalk.green(` ✅ Removed container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already removed)`)\n )\n } else {\n throw error\n }\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to stop/remove container for service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n\n // Handle Nginx configs by domain\n // Check which services remain active for each domain\n if (allDomains.size > 0) {\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx configurations...`))\n\n // Create a set of service names being brought down for quick lookup\n const servicesBeingDowned = new Set(servicesToDown.map((s) => s.name))\n\n // For each domain, find all services that use it (from all config services)\n // and determine which ones remain active\n for (const domain of allDomains) {\n const configName = domain.replace(/\\./g, '_')\n\n // Find all services that use this domain (from entire config)\n const allServicesForDomain = config.services.filter((s) => s.domains.includes(domain))\n\n // Filter out services being brought down\n const remainingServices = allServicesForDomain.filter(\n (s) => !servicesBeingDowned.has(s.name)\n )\n\n // Check if remaining services are actually running (have active containers)\n const activeServices: ServiceConfig[] = []\n for (const service of remainingServices) {\n if (service.docker) {\n const isRunning = await isContainerRunning(service.docker.container)\n if (isRunning) {\n activeServices.push(service)\n }\n } else {\n // Service without docker - assume it's running if not being brought down\n activeServices.push(service)\n }\n }\n\n try {\n if (activeServices.length === 0) {\n // No active services on this domain, disable the config\n console.log(\n chalk.cyan(` 📋 No active services on ${domain}, disabling Nginx config...`)\n )\n await disableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Disabled Nginx config for ${domain}`))\n } else {\n // Regenerate config with remaining active services\n console.log(\n chalk.cyan(\n ` 📋 Regenerating Nginx config for ${domain} with ${\n activeServices.length\n } active service(s): ${activeServices.map((s) => s.name).join(', ')}`\n )\n )\n\n // Generate config for remaining services\n let nginxConfigContent: string\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], false)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, false)\n }\n\n // Check if HTTPS is enabled (check if certificate exists)\n const { certificateExists } = await import('../utils/certbot')\n const hasHttps = await certificateExists(domain)\n if (hasHttps) {\n // Regenerate with HTTPS\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], true)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, true)\n }\n }\n\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(\n chalk.green(\n ` ✅ Updated Nginx config for ${domain} (${activeServices.length} service(s) active)`\n )\n )\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx config for ${domain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload Nginx to apply changes\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx...`))\n try {\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to reload Nginx:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n console.log(chalk.green.bold('\\n✅ Services brought down successfully!\\n'))\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Failed to bring down services:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"down.js","sources":["../../src/commands/down.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport type { ServiceConfig } from '../types/config'\nimport { loadConfig } from '../utils/config-loader'\nimport {\n findServiceByIdentifier,\n getServiceNotFoundError,\n} from '../utils/service-finder'\nimport { isContainerRunning, removeDockerContainer, stopDockerContainer } from '../utils/docker'\nimport {\n disableSite,\n enableSite,\n generateMultiServiceNginxConfig,\n generateNginxConfig,\n reloadNginx,\n writeNginxConfig,\n} from '../utils/nginx'\n\ninterface DownOptions {\n file: string\n all: boolean\n serviceName?: string\n}\n\nexport async function downCommand(options: DownOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n🛑 Bringing Down Services\\n'))\n\n try {\n // Load configuration\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}`))\n\n // Determine which services to bring down\n let servicesToDown: ServiceConfig[] = []\n\n if (options.all) {\n servicesToDown = config.services\n console.log(\n chalk.cyan(\n `📋 Bringing down all services: ${servicesToDown.map((s) => s.name).join(', ')}\\n`\n )\n )\n } else if (options.serviceName) {\n const service = findServiceByIdentifier(config, options.serviceName)\n if (!service) {\n throw new Error(getServiceNotFoundError(options.serviceName, config))\n }\n servicesToDown = [service]\n console.log(chalk.cyan(`📋 Bringing down service: ${service.name}\\n`))\n } else {\n throw new Error('Either specify a service name/index or use --all flag')\n }\n\n // Group services by domain for nginx config management\n const domainToServices = new Map<string, ServiceConfig[]>()\n const allDomains = new Set<string>()\n\n for (const service of servicesToDown) {\n for (const domain of service.domains) {\n allDomains.add(domain)\n if (!domainToServices.has(domain)) {\n domainToServices.set(domain, [])\n }\n domainToServices.get(domain)!.push(service)\n }\n }\n\n // Stop and remove Docker containers\n for (const service of servicesToDown) {\n if (service.docker) {\n console.log(chalk.cyan(`\\n🐳 Stopping Docker container for service: ${service.name}`))\n try {\n const containerName = service.docker.container\n\n // Stop container\n try {\n await stopDockerContainer(containerName)\n console.log(chalk.green(` ✅ Stopped container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already stopped)`)\n )\n } else {\n throw error\n }\n }\n\n // Remove container\n try {\n await removeDockerContainer(containerName)\n console.log(chalk.green(` ✅ Removed container: ${containerName}`))\n } catch (error: any) {\n const errorMessage = error?.message || String(error) || 'Unknown error'\n if (\n errorMessage.toLowerCase().includes('no such container') ||\n errorMessage.toLowerCase().includes('container not found')\n ) {\n console.log(\n chalk.yellow(` ⚠️ Container ${containerName} not found (already removed)`)\n )\n } else {\n throw error\n }\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to stop/remove container for service ${service.name}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n }\n\n // Handle Nginx configs by domain\n // Check which services remain active for each domain\n if (allDomains.size > 0) {\n console.log(chalk.cyan(`\\n⚙️ Updating Nginx configurations...`))\n\n // Create a set of service names being brought down for quick lookup\n const servicesBeingDowned = new Set(servicesToDown.map((s) => s.name))\n\n // For each domain, find all services that use it (from all config services)\n // and determine which ones remain active\n for (const domain of allDomains) {\n const configName = domain.replace(/\\./g, '_')\n\n // Find all services that use this domain (from entire config)\n const allServicesForDomain = config.services.filter((s) => s.domains.includes(domain))\n\n // Filter out services being brought down\n const remainingServices = allServicesForDomain.filter(\n (s) => !servicesBeingDowned.has(s.name)\n )\n\n // Check if remaining services are actually running (have active containers)\n const activeServices: ServiceConfig[] = []\n for (const service of remainingServices) {\n if (service.docker) {\n const isRunning = await isContainerRunning(service.docker.container)\n if (isRunning) {\n activeServices.push(service)\n }\n } else {\n // Service without docker - assume it's running if not being brought down\n activeServices.push(service)\n }\n }\n\n try {\n if (activeServices.length === 0) {\n // No active services on this domain, disable the config\n console.log(\n chalk.cyan(` 📋 No active services on ${domain}, disabling Nginx config...`)\n )\n await disableSite(configName, config.nginx.configPath)\n console.log(chalk.green(` ✅ Disabled Nginx config for ${domain}`))\n } else {\n // Regenerate config with remaining active services\n console.log(\n chalk.cyan(\n ` 📋 Regenerating Nginx config for ${domain} with ${\n activeServices.length\n } active service(s): ${activeServices.map((s) => s.name).join(', ')}`\n )\n )\n\n // Generate config for remaining services\n let nginxConfigContent: string\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], false)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, false)\n }\n\n // Check if HTTPS is enabled (check if certificate exists)\n const { certificateExists } = await import('../utils/certbot')\n const hasHttps = await certificateExists(domain)\n if (hasHttps) {\n // Regenerate with HTTPS\n if (activeServices.length === 1) {\n nginxConfigContent = generateNginxConfig(activeServices[0], true)\n } else {\n nginxConfigContent = generateMultiServiceNginxConfig(activeServices, domain, true)\n }\n }\n\n await writeNginxConfig(configName, config.nginx.configPath, nginxConfigContent)\n await enableSite(configName, config.nginx.configPath)\n console.log(\n chalk.green(\n ` ✅ Updated Nginx config for ${domain} (${activeServices.length} service(s) active)`\n )\n )\n }\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to update Nginx config for ${domain}:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n // Reload Nginx to apply changes\n console.log(chalk.cyan(`\\n🔄 Reloading Nginx...`))\n try {\n await reloadNginx(config.nginx.reloadCommand)\n console.log(chalk.green(` ✅ Nginx reloaded`))\n } catch (error) {\n console.error(\n chalk.red(` ❌ Failed to reload Nginx:`),\n error instanceof Error ? error.message : error\n )\n throw error\n }\n }\n\n console.log(chalk.green.bold('\\n✅ Services brought down successfully!\\n'))\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Failed to bring down services:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,168 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import { loadConfig } from "../utils/config-loader.js";
5
+ import { isContainerRunning } from "../utils/docker.js";
6
+ async function listCommand(options) {
7
+ console.log(chalk.blue.bold("\n📋 Service Status\n"));
8
+ try {
9
+ if (!await fs.pathExists(options.file)) {
10
+ throw new Error(`Configuration file not found: ${options.file}`);
11
+ }
12
+ console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`));
13
+ const config = await loadConfig(options.file);
14
+ console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}
15
+ `));
16
+ const serviceStatuses = [];
17
+ for (const service of config.services) {
18
+ let containerStatus = "not-applicable";
19
+ let nginxStatus = "not-configured";
20
+ if (service.docker) {
21
+ try {
22
+ const isRunning = await isContainerRunning(service.docker.container);
23
+ containerStatus = isRunning ? "running" : "stopped";
24
+ } catch (error) {
25
+ containerStatus = "stopped";
26
+ }
27
+ }
28
+ if (service.domains.length > 0) {
29
+ let hasEnabledConfig = false;
30
+ for (const domain of service.domains) {
31
+ const configName = domain.replace(/\./g, "_");
32
+ const configPath = path.join(config.nginx.configPath, `${configName}.conf`);
33
+ const enabledPath = path.join(
34
+ config.nginx.configPath.replace("sites-available", "sites-enabled"),
35
+ `${configName}.conf`
36
+ );
37
+ try {
38
+ const configFileExists = await fs.pathExists(configPath);
39
+ const enabledFileExists = await fs.pathExists(enabledPath);
40
+ if (configFileExists && enabledFileExists) {
41
+ hasEnabledConfig = true;
42
+ break;
43
+ }
44
+ } catch (error) {
45
+ }
46
+ }
47
+ nginxStatus = hasEnabledConfig ? "enabled" : "disabled";
48
+ } else {
49
+ nginxStatus = "not-configured";
50
+ }
51
+ serviceStatuses.push({
52
+ name: service.name,
53
+ port: service.port,
54
+ domains: service.domains,
55
+ hasDocker: !!service.docker,
56
+ containerName: service.docker?.container,
57
+ containerStatus,
58
+ nginxStatus,
59
+ healthCheck: service.healthCheck
60
+ });
61
+ }
62
+ console.log(chalk.cyan("Services:\n"));
63
+ console.log(
64
+ chalk.bold(
65
+ `${"#".padEnd(4)} ${"Service".padEnd(20)} ${"Status".padEnd(12)} ${"Port".padEnd(8)} ${"Container".padEnd(20)} ${"Nginx".padEnd(12)} ${"Domains"}`
66
+ )
67
+ );
68
+ console.log(chalk.dim("-".repeat(120)));
69
+ for (let i = 0; i < serviceStatuses.length; i++) {
70
+ const status = serviceStatuses[i];
71
+ const index = chalk.dim(`${i + 1}.`.padEnd(4));
72
+ const serviceName = chalk.white(status.name.padEnd(20));
73
+ const overallStatus = getOverallStatus(status);
74
+ const statusDisplay = formatStatus(overallStatus).padEnd(12);
75
+ const portDisplay = chalk.dim(String(status.port).padEnd(8));
76
+ const containerDisplay = status.hasDocker ? formatContainerStatus(status.containerStatus, status.containerName || "").padEnd(20) : chalk.dim("N/A".padEnd(20));
77
+ const nginxDisplay = formatNginxStatus(status.nginxStatus).padEnd(12);
78
+ const domainsDisplay = chalk.dim(status.domains.join(", ") || "N/A");
79
+ console.log(
80
+ `${index} ${serviceName} ${statusDisplay} ${portDisplay} ${containerDisplay} ${nginxDisplay} ${domainsDisplay}`
81
+ );
82
+ }
83
+ console.log();
84
+ const runningCount = serviceStatuses.filter((s) => {
85
+ if (s.hasDocker) {
86
+ return s.containerStatus === "running" && s.nginxStatus === "enabled";
87
+ } else {
88
+ return s.nginxStatus === "enabled";
89
+ }
90
+ }).length;
91
+ const stoppedCount = serviceStatuses.filter((s) => {
92
+ if (s.hasDocker) {
93
+ return s.containerStatus === "stopped" || s.nginxStatus === "disabled";
94
+ } else {
95
+ return s.nginxStatus === "disabled" || s.nginxStatus === "not-configured";
96
+ }
97
+ }).length;
98
+ const totalCount = serviceStatuses.length;
99
+ console.log(chalk.cyan("Summary:"));
100
+ console.log(chalk.green(` ✅ Running: ${runningCount}`));
101
+ console.log(chalk.red(` ❌ Stopped: ${stoppedCount}`));
102
+ console.log(chalk.dim(` 📊 Total: ${totalCount}`));
103
+ console.log();
104
+ } catch (error) {
105
+ console.error(
106
+ chalk.red("\n❌ Failed to list services:"),
107
+ error instanceof Error ? error.message : error
108
+ );
109
+ process.exit(1);
110
+ }
111
+ }
112
+ function getOverallStatus(status) {
113
+ if (status.hasDocker) {
114
+ if (status.containerStatus === "running" && status.nginxStatus === "enabled") {
115
+ return "running";
116
+ } else if (status.containerStatus === "stopped" && status.nginxStatus === "disabled") {
117
+ return "stopped";
118
+ } else {
119
+ return "partial";
120
+ }
121
+ } else {
122
+ if (status.nginxStatus === "enabled") {
123
+ return "running";
124
+ } else {
125
+ return "stopped";
126
+ }
127
+ }
128
+ }
129
+ function formatStatus(status) {
130
+ switch (status) {
131
+ case "running":
132
+ return chalk.green("● Running");
133
+ case "stopped":
134
+ return chalk.red("○ Stopped");
135
+ case "partial":
136
+ return chalk.yellow("⚠ Partial");
137
+ default:
138
+ return chalk.dim("? Unknown");
139
+ }
140
+ }
141
+ function formatContainerStatus(status, containerName) {
142
+ switch (status) {
143
+ case "running":
144
+ return chalk.green(containerName);
145
+ case "stopped":
146
+ return chalk.red(containerName);
147
+ case "not-applicable":
148
+ return chalk.dim("N/A");
149
+ default:
150
+ return chalk.dim("?");
151
+ }
152
+ }
153
+ function formatNginxStatus(status) {
154
+ switch (status) {
155
+ case "enabled":
156
+ return chalk.green("Enabled");
157
+ case "disabled":
158
+ return chalk.red("Disabled");
159
+ case "not-configured":
160
+ return chalk.dim("N/A");
161
+ default:
162
+ return chalk.dim("?");
163
+ }
164
+ }
165
+ export {
166
+ listCommand
167
+ };
168
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sources":["../../src/commands/list.ts"],"sourcesContent":["import chalk from 'chalk'\nimport fs from 'fs-extra'\nimport path from 'path'\nimport { loadConfig } from '../utils/config-loader'\nimport { isContainerRunning } from '../utils/docker'\n\ninterface ListOptions {\n file: string\n}\n\ninterface ServiceStatus {\n name: string\n port: number\n domains: string[]\n hasDocker: boolean\n containerName?: string\n containerStatus: 'running' | 'stopped' | 'not-applicable'\n nginxStatus: 'enabled' | 'disabled' | 'not-configured'\n healthCheck?: {\n path: string\n interval: number\n }\n}\n\nexport async function listCommand(options: ListOptions): Promise<void> {\n console.log(chalk.blue.bold('\\n📋 Service Status\\n'))\n\n try {\n // Load configuration\n if (!(await fs.pathExists(options.file))) {\n throw new Error(`Configuration file not found: ${options.file}`)\n }\n\n console.log(chalk.cyan(`📄 Loading configuration from ${options.file}...`))\n const config = await loadConfig(options.file)\n console.log(chalk.green(`✅ Configuration loaded for project: ${config.project.name}\\n`))\n\n // Collect status for all services\n const serviceStatuses: ServiceStatus[] = []\n\n for (const service of config.services) {\n let containerStatus: 'running' | 'stopped' | 'not-applicable' = 'not-applicable'\n let nginxStatus: 'enabled' | 'disabled' | 'not-configured' = 'not-configured'\n\n // Check Docker container status\n if (service.docker) {\n try {\n const isRunning = await isContainerRunning(service.docker.container)\n containerStatus = isRunning ? 'running' : 'stopped'\n } catch (error) {\n // Container doesn't exist or error checking\n containerStatus = 'stopped'\n }\n }\n\n // Check Nginx configuration status\n // A service is considered enabled if at least one of its domains has an active Nginx config\n if (service.domains.length > 0) {\n let hasEnabledConfig = false\n for (const domain of service.domains) {\n const configName = domain.replace(/\\./g, '_')\n const configPath = path.join(config.nginx.configPath, `${configName}.conf`)\n const enabledPath = path.join(\n config.nginx.configPath.replace('sites-available', 'sites-enabled'),\n `${configName}.conf`\n )\n\n try {\n // Check if config exists in sites-available\n const configFileExists = await fs.pathExists(configPath)\n // Check if symlink exists in sites-enabled\n const enabledFileExists = await fs.pathExists(enabledPath)\n\n if (configFileExists && enabledFileExists) {\n hasEnabledConfig = true\n break\n }\n } catch (error) {\n // Error checking, assume not configured\n }\n }\n\n nginxStatus = hasEnabledConfig ? 'enabled' : 'disabled'\n } else {\n nginxStatus = 'not-configured'\n }\n\n serviceStatuses.push({\n name: service.name,\n port: service.port,\n domains: service.domains,\n hasDocker: !!service.docker,\n containerName: service.docker?.container,\n containerStatus,\n nginxStatus,\n healthCheck: service.healthCheck,\n })\n }\n\n // Display services in a table format\n console.log(chalk.cyan('Services:\\n'))\n\n // Table header\n console.log(\n chalk.bold(\n `${'#'.padEnd(4)} ${'Service'.padEnd(20)} ${'Status'.padEnd(12)} ${'Port'.padEnd(8)} ${'Container'.padEnd(20)} ${'Nginx'.padEnd(12)} ${'Domains'}`\n )\n )\n console.log(chalk.dim('-'.repeat(120)))\n\n // Table rows\n for (let i = 0; i < serviceStatuses.length; i++) {\n const status = serviceStatuses[i]\n const index = chalk.dim(`${i + 1}.`.padEnd(4))\n const serviceName = chalk.white(status.name.padEnd(20))\n const overallStatus = getOverallStatus(status)\n const statusDisplay = formatStatus(overallStatus).padEnd(12)\n const portDisplay = chalk.dim(String(status.port).padEnd(8))\n const containerDisplay = status.hasDocker\n ? formatContainerStatus(status.containerStatus, status.containerName || '').padEnd(20)\n : chalk.dim('N/A'.padEnd(20))\n const nginxDisplay = formatNginxStatus(status.nginxStatus).padEnd(12)\n const domainsDisplay = chalk.dim(status.domains.join(', ') || 'N/A')\n\n console.log(\n `${index} ${serviceName} ${statusDisplay} ${portDisplay} ${containerDisplay} ${nginxDisplay} ${domainsDisplay}`\n )\n }\n\n console.log()\n\n // Summary\n const runningCount = serviceStatuses.filter((s) => {\n if (s.hasDocker) {\n return s.containerStatus === 'running' && s.nginxStatus === 'enabled'\n } else {\n return s.nginxStatus === 'enabled'\n }\n }).length\n const stoppedCount = serviceStatuses.filter((s) => {\n if (s.hasDocker) {\n return s.containerStatus === 'stopped' || s.nginxStatus === 'disabled'\n } else {\n return s.nginxStatus === 'disabled' || s.nginxStatus === 'not-configured'\n }\n }).length\n const totalCount = serviceStatuses.length\n\n console.log(chalk.cyan('Summary:'))\n console.log(chalk.green(` ✅ Running: ${runningCount}`))\n console.log(chalk.red(` ❌ Stopped: ${stoppedCount}`))\n console.log(chalk.dim(` 📊 Total: ${totalCount}`))\n console.log()\n } catch (error) {\n console.error(\n chalk.red('\\n❌ Failed to list services:'),\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n\nfunction getOverallStatus(status: ServiceStatus): 'running' | 'stopped' | 'partial' {\n if (status.hasDocker) {\n // For Docker services, status depends on container\n if (status.containerStatus === 'running' && status.nginxStatus === 'enabled') {\n return 'running'\n } else if (status.containerStatus === 'stopped' && status.nginxStatus === 'disabled') {\n return 'stopped'\n } else {\n return 'partial' // Container running but Nginx disabled, or vice versa\n }\n } else {\n // For non-Docker services, status depends on Nginx\n if (status.nginxStatus === 'enabled') {\n return 'running'\n } else {\n return 'stopped'\n }\n }\n}\n\nfunction formatStatus(status: 'running' | 'stopped' | 'partial'): string {\n switch (status) {\n case 'running':\n return chalk.green('● Running')\n case 'stopped':\n return chalk.red('○ Stopped')\n case 'partial':\n return chalk.yellow('⚠ Partial')\n default:\n return chalk.dim('? Unknown')\n }\n}\n\nfunction formatContainerStatus(\n status: 'running' | 'stopped' | 'not-applicable',\n containerName: string\n): string {\n switch (status) {\n case 'running':\n return chalk.green(containerName)\n case 'stopped':\n return chalk.red(containerName)\n case 'not-applicable':\n return chalk.dim('N/A')\n default:\n return chalk.dim('?')\n }\n}\n\nfunction formatNginxStatus(status: 'enabled' | 'disabled' | 'not-configured'): string {\n switch (status) {\n case 'enabled':\n return chalk.green('Enabled')\n case 'disabled':\n return chalk.red('Disabled')\n case 'not-configured':\n return chalk.dim('N/A')\n default:\n return chalk.dim('?')\n }\n}\n\n"],"names":[],"mappings":";;;;;AAwBA,eAAsB,YAAY,SAAqC;AACrE,UAAQ,IAAI,MAAM,KAAK,KAAK,uBAAuB,CAAC;AAEpD,MAAI;AAEF,QAAI,CAAE,MAAM,GAAG,WAAW,QAAQ,IAAI,GAAI;AACxC,YAAM,IAAI,MAAM,iCAAiC,QAAQ,IAAI,EAAE;AAAA,IACjE;AAEA,YAAQ,IAAI,MAAM,KAAK,iCAAiC,QAAQ,IAAI,KAAK,CAAC;AAC1E,UAAM,SAAS,MAAM,WAAW,QAAQ,IAAI;AAC5C,YAAQ,IAAI,MAAM,MAAM,uCAAuC,OAAO,QAAQ,IAAI;AAAA,CAAI,CAAC;AAGvF,UAAM,kBAAmC,CAAA;AAEzC,eAAW,WAAW,OAAO,UAAU;AACrC,UAAI,kBAA4D;AAChE,UAAI,cAAyD;AAG7D,UAAI,QAAQ,QAAQ;AAClB,YAAI;AACF,gBAAM,YAAY,MAAM,mBAAmB,QAAQ,OAAO,SAAS;AACnE,4BAAkB,YAAY,YAAY;AAAA,QAC5C,SAAS,OAAO;AAEd,4BAAkB;AAAA,QACpB;AAAA,MACF;AAIA,UAAI,QAAQ,QAAQ,SAAS,GAAG;AAC9B,YAAI,mBAAmB;AACvB,mBAAW,UAAU,QAAQ,SAAS;AACpC,gBAAM,aAAa,OAAO,QAAQ,OAAO,GAAG;AAC5C,gBAAM,aAAa,KAAK,KAAK,OAAO,MAAM,YAAY,GAAG,UAAU,OAAO;AAC1E,gBAAM,cAAc,KAAK;AAAA,YACvB,OAAO,MAAM,WAAW,QAAQ,mBAAmB,eAAe;AAAA,YAClE,GAAG,UAAU;AAAA,UAAA;AAGf,cAAI;AAEF,kBAAM,mBAAmB,MAAM,GAAG,WAAW,UAAU;AAEvD,kBAAM,oBAAoB,MAAM,GAAG,WAAW,WAAW;AAEzD,gBAAI,oBAAoB,mBAAmB;AACzC,iCAAmB;AACnB;AAAA,YACF;AAAA,UACF,SAAS,OAAO;AAAA,UAEhB;AAAA,QACF;AAEA,sBAAc,mBAAmB,YAAY;AAAA,MAC/C,OAAO;AACL,sBAAc;AAAA,MAChB;AAEA,sBAAgB,KAAK;AAAA,QACnB,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,SAAS,QAAQ;AAAA,QACjB,WAAW,CAAC,CAAC,QAAQ;AAAA,QACrB,eAAe,QAAQ,QAAQ;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,aAAa,QAAQ;AAAA,MAAA,CACtB;AAAA,IACH;AAGA,YAAQ,IAAI,MAAM,KAAK,aAAa,CAAC;AAGrC,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ,GAAG,IAAI,OAAO,CAAC,CAAC,IAAI,UAAU,OAAO,EAAE,CAAC,IAAI,SAAS,OAAO,EAAE,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC,IAAI,YAAY,OAAO,EAAE,CAAC,IAAI,QAAQ,OAAO,EAAE,CAAC,IAAI,SAAS;AAAA,MAAA;AAAA,IAClJ;AAEF,YAAQ,IAAI,MAAM,IAAI,IAAI,OAAO,GAAG,CAAC,CAAC;AAGtC,aAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,YAAM,SAAS,gBAAgB,CAAC;AAChC,YAAM,QAAQ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC;AAC7C,YAAM,cAAc,MAAM,MAAM,OAAO,KAAK,OAAO,EAAE,CAAC;AACtD,YAAM,gBAAgB,iBAAiB,MAAM;AAC7C,YAAM,gBAAgB,aAAa,aAAa,EAAE,OAAO,EAAE;AAC3D,YAAM,cAAc,MAAM,IAAI,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3D,YAAM,mBAAmB,OAAO,YAC5B,sBAAsB,OAAO,iBAAiB,OAAO,iBAAiB,EAAE,EAAE,OAAO,EAAE,IACnF,MAAM,IAAI,MAAM,OAAO,EAAE,CAAC;AAC9B,YAAM,eAAe,kBAAkB,OAAO,WAAW,EAAE,OAAO,EAAE;AACpE,YAAM,iBAAiB,MAAM,IAAI,OAAO,QAAQ,KAAK,IAAI,KAAK,KAAK;AAEnE,cAAQ;AAAA,QACN,GAAG,KAAK,IAAI,WAAW,IAAI,aAAa,IAAI,WAAW,IAAI,gBAAgB,IAAI,YAAY,IAAI,cAAc;AAAA,MAAA;AAAA,IAEjH;AAEA,YAAQ,IAAA;AAGR,UAAM,eAAe,gBAAgB,OAAO,CAAC,MAAM;AACjD,UAAI,EAAE,WAAW;AACf,eAAO,EAAE,oBAAoB,aAAa,EAAE,gBAAgB;AAAA,MAC9D,OAAO;AACL,eAAO,EAAE,gBAAgB;AAAA,MAC3B;AAAA,IACF,CAAC,EAAE;AACH,UAAM,eAAe,gBAAgB,OAAO,CAAC,MAAM;AACjD,UAAI,EAAE,WAAW;AACf,eAAO,EAAE,oBAAoB,aAAa,EAAE,gBAAgB;AAAA,MAC9D,OAAO;AACL,eAAO,EAAE,gBAAgB,cAAc,EAAE,gBAAgB;AAAA,MAC3D;AAAA,IACF,CAAC,EAAE;AACH,UAAM,aAAa,gBAAgB;AAEnC,YAAQ,IAAI,MAAM,KAAK,UAAU,CAAC;AAClC,YAAQ,IAAI,MAAM,MAAM,gBAAgB,YAAY,EAAE,CAAC;AACvD,YAAQ,IAAI,MAAM,IAAI,gBAAgB,YAAY,EAAE,CAAC;AACrD,YAAQ,IAAI,MAAM,IAAI,eAAe,UAAU,EAAE,CAAC;AAClD,YAAQ,IAAA;AAAA,EACV,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,MAAM,IAAI,8BAA8B;AAAA,MACxC,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAAA;AAE3C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,SAAS,iBAAiB,QAA0D;AAClF,MAAI,OAAO,WAAW;AAEpB,QAAI,OAAO,oBAAoB,aAAa,OAAO,gBAAgB,WAAW;AAC5E,aAAO;AAAA,IACT,WAAW,OAAO,oBAAoB,aAAa,OAAO,gBAAgB,YAAY;AACpF,aAAO;AAAA,IACT,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF,OAAO;AAEL,QAAI,OAAO,gBAAgB,WAAW;AACpC,aAAO;AAAA,IACT,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,aAAa,QAAmD;AACvE,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,aAAO,MAAM,MAAM,WAAW;AAAA,IAChC,KAAK;AACH,aAAO,MAAM,IAAI,WAAW;AAAA,IAC9B,KAAK;AACH,aAAO,MAAM,OAAO,WAAW;AAAA,IACjC;AACE,aAAO,MAAM,IAAI,WAAW;AAAA,EAAA;AAElC;AAEA,SAAS,sBACP,QACA,eACQ;AACR,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,aAAO,MAAM,MAAM,aAAa;AAAA,IAClC,KAAK;AACH,aAAO,MAAM,IAAI,aAAa;AAAA,IAChC,KAAK;AACH,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB;AACE,aAAO,MAAM,IAAI,GAAG;AAAA,EAAA;AAE1B;AAEA,SAAS,kBAAkB,QAA2D;AACpF,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,aAAO,MAAM,MAAM,SAAS;AAAA,IAC9B,KAAK;AACH,aAAO,MAAM,IAAI,UAAU;AAAA,IAC7B,KAAK;AACH,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB;AACE,aAAO,MAAM,IAAI,GAAG;AAAA,EAAA;AAE1B;"}