@brewnet/cli 0.0.1 → 0.0.2
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.
- package/dist/admin-server-UODBPGWR.js +16 -0
- package/dist/app-manager-FIHPVUP7.js +51 -0
- package/dist/{boilerplate-manager-P6QYUU7Q.js → boilerplate-manager-WEFTHL2O.js} +3 -3
- package/dist/{chunk-DH2VK3YI.js → chunk-54WFZCU6.js} +2 -2
- package/dist/{chunk-4TJMJZMO.js → chunk-AXSHZEB3.js} +63 -1
- package/dist/{chunk-4TJMJZMO.js.map → chunk-AXSHZEB3.js.map} +1 -1
- package/dist/chunk-FZZ3HP2G.js +1151 -0
- package/dist/chunk-FZZ3HP2G.js.map +1 -0
- package/dist/chunk-JIPAYMOA.js +58 -0
- package/dist/chunk-JIPAYMOA.js.map +1 -0
- package/dist/{chunk-SIXBB6JU.js → chunk-Q6UUZR2V.js} +238 -1171
- package/dist/chunk-Q6UUZR2V.js.map +1 -0
- package/dist/{chunk-2VWMDHGI.js → chunk-YAYXULLO.js} +9 -61
- package/dist/chunk-YAYXULLO.js.map +1 -0
- package/dist/{chunk-JFPHGZ6Z.js → chunk-YXFDB5YX.js} +17 -2
- package/dist/chunk-YXFDB5YX.js.map +1 -0
- package/dist/{cloudflare-client-TFT6VCXF.js → cloudflare-client-F2TGQXGS.js} +2 -2
- package/dist/{compose-generator-O7GSIJ2S.js → compose-generator-OFJ2YWMB.js} +4 -2
- package/dist/compose-generator-OFJ2YWMB.js.map +1 -0
- package/dist/index.js +122 -168
- package/dist/index.js.map +1 -1
- package/dist/services/admin-daemon.js +6 -4
- package/dist/services/admin-daemon.js.map +1 -1
- package/package.json +1 -1
- package/dist/admin-server-DQVIEHV3.js +0 -14
- package/dist/chunk-2VWMDHGI.js.map +0 -1
- package/dist/chunk-JFPHGZ6Z.js.map +0 -1
- package/dist/chunk-SIXBB6JU.js.map +0 -1
- /package/dist/{admin-server-DQVIEHV3.js.map → admin-server-UODBPGWR.js.map} +0 -0
- /package/dist/{boilerplate-manager-P6QYUU7Q.js.map → app-manager-FIHPVUP7.js.map} +0 -0
- /package/dist/{cloudflare-client-TFT6VCXF.js.map → boilerplate-manager-WEFTHL2O.js.map} +0 -0
- /package/dist/{chunk-DH2VK3YI.js.map → chunk-54WFZCU6.js.map} +0 -0
- /package/dist/{compose-generator-O7GSIJ2S.js.map → cloudflare-client-F2TGQXGS.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/admin-server.ts"],"sourcesContent":["/**\n * Brewnet CLI — Local Admin Panel Server (T101a)\n *\n * Node.js built-in HTTP server serving:\n * - Static HTML dashboard at GET /\n * - REST API per contracts/admin-api.md\n *\n * Port default: 8088 (localhost-only, no auth required)\n *\n * @module services/admin-server\n */\n\nimport { createServer, IncomingMessage, ServerResponse, Server } from 'node:http';\nimport { createConnection } from 'node:net';\nimport { join, resolve, extname } from 'node:path';\nimport { existsSync, readFileSync, writeFileSync, statSync, createReadStream } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport Dockerode from 'dockerode';\nimport { addService, removeService } from './service-manager.js';\nimport { createBackup, listBackups } from './backup-manager.js';\nimport { getServiceDefinition, SERVICE_REGISTRY } from '../config/services.js';\n// SERVICE_DETAIL_MAP inlined from deleted status-page.ts (T044/T045)\nimport { getLastProject, loadState } from '../wizard/state.js';\nimport { logger } from '../utils/logger.js';\nimport { DomainManager } from './domain-manager.js';\nimport { verifyToken } from './cloudflare-client.js';\nimport type { WizardState, LogSource, UnifiedLogLevel } from '@brewnet/shared';\nimport { queryLogs, getLogStats } from '../utils/log-aggregator.js';\n// apps-page.ts import removed — HTML generation replaced by React SPA (T044)\nimport { createApp, getJobStatus, listApps, startApp, stopApp, removeApp as appRemove, getDeployHistory, listGiteaRepos, deployApp, rollbackApp, getAppGitInfo, getAppBranches, updateDeploySettings, getDeploySettings, getAppDir, detectBasePath } from './app-manager.js';\nimport type { DeploySettings } from '../types/app-entry.js';\nimport type { CreateAppOptions } from '../types/app-entry.js';\nimport { getStackById } from '../config/stacks.js';\n\n// ---------------------------------------------------------------------------\n// Types (per admin-api.md)\n// ---------------------------------------------------------------------------\n\nexport interface ServiceStatus {\n id: string;\n name: string;\n type: string;\n status: 'running' | 'stopped' | 'error' | 'not_installed';\n cpu: string;\n memory: string;\n uptime: string;\n port: number | null;\n url: string | null;\n externalUrl: string | null;\n removable: boolean;\n stackId?: string;\n}\n\nexport interface AdminServerOptions {\n port?: number;\n projectPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// HTML Dashboard (inline, dynamically generated with embedded config)\n// ---------------------------------------------------------------------------\n\n/** Shape of a single stack entry in .brewnet-boilerplate.json */\ninterface BoilerplateMeta {\n stackId: string;\n appDir?: string;\n backendUrl?: string;\n frontendUrl?: string;\n isUnified?: boolean;\n lang?: string;\n frameworkId?: string;\n dbDriver?: string;\n dbUser?: string;\n dbName?: string;\n gitBranch?: string;\n status?: string;\n}\n\ninterface DashboardConfig {\n adminUsername: string;\n passwordHint: string;\n domainProvider: string;\n quickTunnelUrl: string;\n zoneName: string;\n tunnelId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Static icon assets — resolved once at module load from public/images/\n// ---------------------------------------------------------------------------\n\nconst PKG_ROOT = join(fileURLToPath(import.meta.url), '../../../..');\n\n// ---------------------------------------------------------------------------\n// Static file serving for React SPA (packages/admin-ui/dist)\n// ---------------------------------------------------------------------------\n\nconst ADMIN_UI_DIST = join(PKG_ROOT, 'packages/admin-ui/dist');\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.mjs': 'application/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.webmanifest': 'application/manifest+json',\n};\n\nfunction serveStaticFile(filePath: string, res: ServerResponse, statusCode = 200): void {\n const stat = statSync(filePath);\n const mime = MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n const isHashed = new RegExp('assets/[^/]+-[A-Za-z0-9]{8,}\\\\.[^.]+$').test(filePath);\n const cacheControl = isHashed ? 'public, max-age=31536000, immutable' : 'no-cache, no-store, must-revalidate';\n res.writeHead(statusCode, {\n 'Content-Type': mime,\n 'Content-Length': stat.size,\n 'Cache-Control': cacheControl,\n });\n createReadStream(filePath).pipe(res);\n}\n\n/** Brewnet SVG icon (inline string, served at /icon.svg) */\nconst ICON_SVG = (() => {\n const candidates = [\n join(PKG_ROOT, 'public/images/icon.svg'),\n join(PKG_ROOT, '../public/images/icon.svg'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return readFileSync(p, 'utf-8');\n }\n // Fallback: inline SVG (amber mug-wifi icon)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"4 6 38 38\" fill=\"none\" stroke=\"#f5a623\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 26H32V34C32 36.8 29.8 39 27 39H13C10.2 39 8 36.8 8 34V26Z\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M32 28.5C35.5 28.5 37 30.5 37 32.5C37 34.5 35.5 36.5 32 36.5\" stroke-width=\"3.5\" fill=\"none\"/><circle cx=\"20\" cy=\"30\" r=\"2.2\" fill=\"#f5a623\" stroke=\"none\"/><path d=\"M16.5 20a5 5 0 0 1 7 0\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M13.5 15.5a10 10 0 0 1 13 0\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M10.5 11a15 15 0 0 1 19 0\" stroke-width=\"3.5\" fill=\"none\"/></svg>`;\n})();\n\n/** Brewnet favicon.ico (binary Buffer, served at /favicon.ico) */\nconst FAVICON_ICO = (() => {\n const candidates = [\n join(PKG_ROOT, 'public/images/favicon.ico'),\n join(PKG_ROOT, '../public/images/favicon.ico'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return readFileSync(p);\n }\n return null;\n})();\n\n// ---------------------------------------------------------------------------\n// Service catalog data — moved from deleted status-page.ts (T044/T045)\n// ---------------------------------------------------------------------------\n\ninterface ServiceDetailInfo {\n description: string;\n license: string;\n docs: string;\n features: string[];\n credentials: {\n method: 'env' | 'wizard' | 'cli' | 'basicauth' | 'none';\n summary: string;\n command?: string;\n };\n connectionParams?: { label: string; value: string }[];\n tips: string[];\n securityNote?: string;\n}\n\nconst SERVICE_DETAIL_MAP: Record<string, ServiceDetailInfo> = {\n Traefik: {\n description: 'Go-based open-source reverse proxy and load balancer',\n license: 'MIT',\n docs: 'https://traefik.io/traefik/',\n features: [\n 'Docker label-based automatic service discovery',\n 'Let\\'s Encrypt certificate auto-renewal',\n 'Built-in web dashboard for route monitoring',\n 'Middleware chain: BasicAuth, Rate Limit, IP Whitelist',\n 'HTTP to HTTPS automatic redirect',\n ],\n credentials: {\n method: 'basicauth',\n summary: 'No login in dev mode (--api.insecure=true). Use BasicAuth middleware for production.',\n command: 'htpasswd -nb admin YOUR_PASSWORD',\n },\n tips: [\n 'Remove --api.insecure=true in production and add BasicAuth or Authelia',\n 'Set exposedbydefault=false and explicitly enable each service with traefik.enable=true',\n 'Add --certificatesresolvers.le.acme.email=YOUR_EMAIL for Let\\'s Encrypt',\n ],\n securityNote: '보안상 외부 도메인으로 노출하지 않습니다. 서버 내부(localhost)에서만 접근 가능합니다.',\n },\n 'Traefik Dashboard': {\n description: 'Built-in Traefik web UI for monitoring routes, services, and middleware',\n license: 'MIT',\n docs: 'https://doc.traefik.io/traefik/operations/dashboard/',\n features: [\n 'Real-time view of HTTP/TCP routers',\n 'Service health and load balancer status',\n 'Middleware chain visualization',\n ],\n credentials: {\n method: 'none',\n summary: 'No authentication in dev mode (--api.insecure=true). Protected by BasicAuth in production.',\n },\n tips: [\n 'Dashboard URL requires trailing slash: /dashboard/',\n 'Secure with BasicAuth middleware before exposing externally',\n ],\n },\n Gitea: {\n description: 'Lightweight self-hosted Git service written in Go',\n license: 'MIT',\n docs: 'https://about.gitea.com/',\n features: [\n 'GitHub-like web UI with issues, PRs, wiki, project boards',\n 'Gitea Actions — GitHub Actions compatible CI/CD',\n 'Low memory footprint (~200 MB)',\n 'LDAP, OAuth2, SMTP authentication support',\n 'PostgreSQL, MySQL, SQLite backend support',\n ],\n credentials: {\n method: 'wizard',\n summary: 'First visit shows Installation Wizard. Create admin account in \"Administrator Account Settings\" section.',\n command: 'docker exec -it brewnet-gitea gitea admin user create --username admin --password PASSWORD --email admin@brewnet.dev --admin',\n },\n tips: [\n 'Set DISABLE_REGISTRATION=true to allow only admin-created accounts',\n 'Set REQUIRE_SIGNIN_VIEW=true to prevent anonymous repo browsing',\n 'SSH port mapped to 3022 to avoid conflict with host SSH (22)',\n ],\n },\n Nextcloud: {\n description: 'Self-hosted cloud storage platform (Google Drive/Dropbox alternative)',\n license: 'AGPL-3.0',\n docs: 'https://nextcloud.com/',\n features: [\n 'File sync, sharing, and collaboration',\n '200+ app extensions: calendar, contacts, notes, office docs',\n 'WebDAV protocol support',\n 'Desktop and mobile clients available',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step of brewnet init wizard.',\n command: 'docker exec -u www-data brewnet-nextcloud php occ user:add USERNAME --display-name=\"Display Name\"',\n },\n tips: [\n 'Redis connection recommended for file locking and cache performance',\n 'Add all access domains/IPs to NEXTCLOUD_TRUSTED_DOMAINS',\n 'Switch background jobs to cron: docker exec -u www-data brewnet-nextcloud php occ background:cron',\n ],\n },\n PostgreSQL: {\n description: 'Advanced open-source relational database',\n license: 'PostgreSQL (BSD-like)',\n docs: 'https://www.postgresql.org/',\n features: [\n 'Full ACID compliance with MVCC',\n 'Native JSON/JSONB support',\n 'Full-text search, PostGIS, time-series extensions',\n 'Logical and physical replication',\n ],\n credentials: {\n method: 'env',\n summary: 'Configured via POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB environment variables.',\n command: 'docker exec -it brewnet-postgresql psql -U brewnet -d brewnet_db',\n },\n connectionParams: [\n { label: 'host', value: 'localhost' },\n { label: 'port', value: '5432' },\n { label: 'user', value: 'brewnet' },\n { label: 'db', value: 'brewnet_db' },\n ],\n tips: [\n 'Internal network only (brewnet-internal) — no host port exposed',\n 'Data persisted in named volume — safe across container restarts',\n 'Use init SQL scripts in docker-entrypoint-initdb.d/ for multi-DB setup',\n ],\n },\n MySQL: {\n description: 'Popular open-source relational database',\n license: 'GPL-2.0',\n docs: 'https://www.mysql.com/',\n features: [\n 'InnoDB storage engine with ACID transactions',\n 'JSON support and document store',\n 'Replication and clustering',\n 'Widely supported by web applications',\n ],\n credentials: {\n method: 'env',\n summary: 'Configured via MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD environment variables.',\n command: 'docker exec -it brewnet-mysql mysql -u brewnet -p brewnet_db',\n },\n connectionParams: [\n { label: 'host', value: 'localhost' },\n { label: 'port', value: '3306' },\n { label: 'user', value: 'brewnet' },\n { label: 'db', value: 'brewnet_db' },\n ],\n tips: [\n 'Internal network only (brewnet-internal) — no host port exposed',\n 'Root password required at first startup',\n 'Init SQL scripts run once from docker-entrypoint-initdb.d/',\n ],\n },\n Redis: {\n description: 'In-memory key-value store for caching and message brokering',\n license: 'BSD-3',\n docs: 'https://redis.io/',\n features: [\n 'Session storage, cache, message queue, Pub/Sub',\n 'Single-threaded event loop — 100K+ ops/sec',\n 'RDB + AOF persistence support',\n 'Used by Nextcloud file locking and Gitea caching',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with --requirepass flag.',\n command: 'docker exec -it brewnet-redis redis-cli ping',\n },\n tips: [\n 'Set --maxmemory and --maxmemory-policy to prevent unbounded memory growth',\n 'Internal network only — no host port exposed',\n 'Redis 6+ supports ACL for multi-user access control',\n ],\n },\n pgAdmin: {\n description: 'Web-based administration tool for PostgreSQL',\n license: 'PostgreSQL (BSD-like)',\n docs: 'https://www.pgadmin.org/',\n features: [\n 'SQL editor with query execution and plan visualization',\n 'Table, index, view, and function GUI management',\n 'Backup and restore (pg_dump, pg_restore)',\n 'Multi-server management via server groups',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step. Email format: {username}@brewnet.dev. Register the DB server after first login.',\n },\n tips: [\n 'Connect to PostgreSQL using hostname \"postgresql\" (Docker container name), port 5432',\n 'Set PGADMIN_CONFIG_SERVER_MODE=False to skip login in dev mode',\n 'Mount servers.json to auto-register DB servers on startup',\n ],\n },\n Jellyfin: {\n description: 'Open-source media server (Plex/Emby free alternative)',\n license: 'GPL-2.0',\n docs: 'https://jellyfin.org/',\n features: [\n 'Movies, TV, music, photos, and live TV/DVR',\n 'Hardware transcoding (Intel QSV, NVIDIA NVENC, VAAPI)',\n 'Clients for web, Android, iOS, Roku, Fire TV, Kodi',\n 'DLNA support',\n ],\n credentials: {\n method: 'wizard',\n summary: 'First visit shows Setup Wizard. Create admin account in step 2 (User).',\n },\n tips: [\n 'Mount media folders as read-only (:ro) for safety',\n 'Add --device=/dev/dri:/dev/dri for Intel GPU hardware transcoding',\n 'DLNA requires --net=host (does not work in Docker bridge mode)',\n ],\n },\n 'SSH Server': {\n description: 'Industry-standard remote access via OpenSSH in Docker',\n license: 'BSD',\n docs: 'https://www.openssh.com/',\n features: [\n 'Key-based authentication (more secure than passwords)',\n 'Built-in SFTP — no separate FTP server needed',\n 'Port forwarding and tunneling support',\n 'Remote management entry point for Brewnet containers',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin username set in Pre-Step. Password auth enabled (PASSWORD_ACCESS=true); switch to key-only after setup.',\n command: 'ssh -p 2222 USER@localhost',\n },\n connectionParams: [\n { label: 'host', value: 'localhost' },\n { label: 'port', value: '2222' },\n { label: 'user', value: '<admin-username>' },\n { label: 'protocol', value: 'SSH / SFTP' },\n ],\n tips: [\n 'Switch to key-only auth after initial setup: set PASSWORD_ACCESS=false',\n 'Port 2222 avoids conflict with host SSH (port 22)',\n 'SFTP runs as SSH subsystem — no separate container needed',\n ],\n },\n FileBrowser: {\n description: 'Lightweight web-based file manager written in Go',\n license: 'Apache-2.0',\n docs: 'https://filebrowser.org/',\n features: [\n 'Upload, download, edit, and delete files via browser',\n 'Multi-user support with per-user directory scoping',\n 'Built-in code editor for text files',\n 'Share link generation and shell command execution',\n ],\n credentials: {\n method: 'none',\n summary: 'Default user: admin. Random password printed to container logs on first start.',\n command: 'docker logs brewnet-filebrowser | grep \"password\"',\n },\n tips: [\n 'Initial password is shown only once in logs — change it immediately',\n 'Set per-user Scope to restrict directory access',\n 'All settings and user data stored in filebrowser.db file',\n ],\n },\n 'MinIO Console': {\n description: 'S3-compatible object storage with a web console',\n license: 'AGPL-3.0',\n docs: 'https://min.io/',\n features: [\n 'Amazon S3-compatible API',\n 'Web console for bucket and object management',\n 'Erasure coding and bitrot protection',\n 'Multi-user IAM with policies',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step of brewnet init wizard.',\n },\n tips: [\n 'Console on port 9001, API on port 9000',\n 'Create IAM users with limited policies for application access',\n 'Use mc (MinIO Client) CLI for scripted bucket management',\n ],\n },\n Cloudflared: {\n description: 'Cloudflare Tunnel daemon — exposes local services to the internet securely',\n license: 'Apache-2.0',\n docs: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/',\n features: [\n 'No port forwarding or public IP required',\n 'Automatic SSL/TLS via Cloudflare',\n 'Quick Tunnel (*.trycloudflare.com) or Named Tunnel with custom domain',\n 'DDoS protection included',\n ],\n credentials: {\n method: 'none',\n summary: 'No login. Quick Tunnel needs no account. Named Tunnel uses TUNNEL_TOKEN from Cloudflare API.',\n },\n tips: [\n 'Quick Tunnel URL changes on every restart — use Named Tunnel for permanent access',\n 'Check tunnel status: brewnet domain tunnel status',\n 'Audit logs saved to ~/.brewnet/logs/tunnel.log',\n ],\n },\n Nginx: {\n description: 'High-performance HTTP and reverse proxy server',\n license: 'BSD-2',\n docs: 'https://nginx.org/',\n features: [\n 'Event-driven architecture — handles 10K+ concurrent connections',\n 'Static file serving and reverse proxy',\n 'Load balancing with multiple algorithms',\n 'SSL/TLS termination',\n ],\n credentials: {\n method: 'none',\n summary: 'No built-in authentication. Use auth_basic module or upstream auth for protection.',\n },\n tips: [\n 'Default config serves welcome page on port 80',\n 'Use location blocks for path-based routing to upstream services',\n 'Reload config without downtime: nginx -s reload',\n ],\n },\n Caddy: {\n description: 'Modern web server with automatic HTTPS',\n license: 'Apache-2.0',\n docs: 'https://caddyserver.com/',\n features: [\n 'Automatic HTTPS with Let\\'s Encrypt (zero config)',\n 'HTTP/2 and HTTP/3 support out of the box',\n 'Simple Caddyfile configuration',\n 'Reverse proxy with health checks',\n ],\n credentials: {\n method: 'none',\n summary: 'No built-in authentication. Use basicauth directive in Caddyfile for protection.',\n },\n tips: [\n 'Caddyfile syntax is simpler than Nginx — great for small setups',\n 'Automatic certificate management requires ports 80 and 443',\n 'Use caddy reload for config changes without downtime',\n ],\n },\n Valkey: {\n description: 'Open-source, high-performance Redis-compatible in-memory data store (Linux Foundation fork)',\n license: 'BSD-3',\n docs: 'https://valkey.io/',\n features: [\n 'Drop-in Redis replacement — fully API compatible',\n 'Session storage, cache, message queue, Pub/Sub',\n 'RDB + AOF persistence support',\n 'Active community-driven development post Redis license change',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with --requirepass flag.',\n command: 'docker exec -it brewnet-valkey valkey-cli ping',\n },\n tips: [\n 'Set --maxmemory and --maxmemory-policy to prevent unbounded memory growth',\n 'Internal network only — no host port exposed',\n 'Use OBJECT ENCODING to inspect memory layout of individual keys',\n ],\n },\n KeyDB: {\n description: 'Multithreaded Redis-compatible in-memory database with higher throughput',\n license: 'BSD-3',\n docs: 'https://docs.keydb.dev/',\n features: [\n 'Multi-threaded architecture — higher throughput than Redis on multi-core CPUs',\n 'Active-Active replication for multi-master setups',\n 'FLASH storage support for large datasets exceeding RAM',\n 'Drop-in Redis replacement — fully API compatible',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with requirepass config.',\n command: 'docker exec -it brewnet-keydb keydb-cli ping',\n },\n tips: [\n 'Set server-threads to number of CPU cores for best performance',\n 'Internal network only — no host port exposed',\n 'Use keydb-cli --stat to monitor live throughput',\n ],\n },\n};\n\n/**\n * Name alias map: SERVICE_REGISTRY display names → SERVICE_DETAIL_MAP keys.\n * Only entries that differ need to be listed here.\n */\nconst NAME_ALIASES: Record<string, string> = {\n 'OpenSSH Server': 'SSH Server',\n 'Cloudflare Tunnel': 'Cloudflared',\n 'MinIO': 'MinIO Console',\n};\n\n// ---------------------------------------------------------------------------\n// // Docker helpers\n// ---------------------------------------------------------------------------\n\nconst docker = new Dockerode();\n\nconst REQUIRED_SERVICES = new Set(['traefik', 'nginx', 'caddy', 'gitea']);\n\nconst INTERNAL_SERVICES = new Set(['brewnet-welcome', 'brewnet-landing', 'cloudflared']);\n\n// Non-HTTP services that should never show a clickable local URL.\n// All other services with a public TCP port get http://localhost:<port>.\nconst NO_HTTP_SERVICES = new Set([\n 'postgresql', 'mysql', 'mariadb',\n 'openssh-server',\n]);\n\n// Services that must be accessed through Traefik path-prefix routing.\n// Their OVERWRITEWEBROOT / SCRIPT_NAME settings make direct-port access broken.\nconst TRAEFIK_PATH_SERVICES: Record<string, string> = {\n traefik: 'http://localhost/dashboard/',\n gitea: 'http://localhost/git',\n nextcloud: 'http://localhost/cloud',\n pgadmin: 'http://localhost:5050/pgadmin',\n};\n\n// Known SSH ports that should never be used as the primary HTTP port.\nconst KNOWN_SSH_PORTS = new Set([22, 2222, 3022]);\n\nfunction getPrimaryPort(container: Dockerode.ContainerInfo): number | null {\n const tcp = (container.Ports ?? [])\n .filter((p) => p.Type === 'tcp' && p.PublicPort && !KNOWN_SSH_PORTS.has(p.PublicPort))\n .sort((a, b) => a.PublicPort! - b.PublicPort!);\n return tcp[0]?.PublicPort ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Request router\n// ---------------------------------------------------------------------------\n\nfunction json(res: ServerResponse, status: number, data: unknown): void {\n const payload = JSON.stringify(data);\n res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });\n res.end(payload);\n}\n\nasync function readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve) => {\n let body = '';\n req.on('data', (c) => (body += c));\n req.on('end', () => resolve(body));\n });\n}\n\nasync function handleGetServices(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n _projectPath: string,\n urlMap: Record<string, string> = TRAEFIK_PATH_SERVICES,\n quickTunnelUrl = '',\n allowedDirs?: Set<string>,\n wizardState?: WizardState | null,\n): Promise<void> {\n // Named Tunnel subdomain map — composeService → fixed subdomain prefix\n const NAMED_SUBDOMAIN_MAP: Record<string, string> = {\n gitea: 'git',\n nextcloud: 'cloud',\n jellyfin: 'media',\n filebrowser: 'files',\n pgadmin: 'pgadmin',\n minio: 'minio',\n };\n try {\n const allContainers = await docker.listContainers({ all: true });\n const services: ServiceStatus[] = [];\n\n for (const c of allContainers) {\n const composeService = c.Labels?.['com.docker.compose.service'];\n if (!composeService) continue;\n if (INTERNAL_SERVICES.has(composeService)) continue;\n\n const workingDir = c.Labels?.['com.docker.compose.project.working_dir'] ?? '';\n\n // Skip containers from unselected boilerplate stacks.\n // A container whose working_dir is under projectPath but NOT in allowedDirs\n // is an unselected boilerplate stack that shouldn't appear in the dashboard.\n if (allowedDirs && allowedDirs.size > 0) {\n if (workingDir && workingDir.startsWith(_projectPath) && !allowedDirs.has(workingDir)) {\n continue;\n }\n }\n\n const def = getServiceDefinition(composeService);\n const s = c.State as string;\n const status = s === 'running' ? 'running' : s === 'exited' ? 'stopped' : ('error' as const);\n const port = getPrimaryPort(c) ?? def?.ports?.[0] ?? null;\n\n // Detect Traefik PathPrefix and strip-prefix from container labels.\n // A container may have multiple PathPrefix routers (e.g. FileBrowser has\n // /files as main route and /static as an extra asset route). We need the\n // primary router whose name matches `quicktunnel-{serviceId}` exactly,\n // not auxiliary routers like `quicktunnel-{serviceId}-static`.\n const labels = c.Labels ?? {};\n const primaryRouterKey = `traefik.http.routers.quicktunnel-${composeService}.rule`;\n const routerRule: [string, string] | undefined =\n labels[primaryRouterKey] && String(labels[primaryRouterKey]).includes('PathPrefix')\n ? [primaryRouterKey, labels[primaryRouterKey]]\n : Object.entries(labels).find(\n ([k, v]) => k.includes('traefik.http.routers.') && k.endsWith('.rule') && String(v).includes('PathPrefix'),\n );\n let traefikPath = '';\n if (routerRule) {\n const pathMatch = String(routerRule[1]).match(/PathPrefix\\(`([^`]+)`\\)/);\n if (pathMatch) traefikPath = pathMatch[1]!;\n }\n // basePath stacks (e.g. Next.js): PathPrefix exists but no strip-prefix middleware.\n // The app serves content at the sub-path even on direct port access.\n const hasStripPrefix = Object.keys(labels).some(\n (k) => k.includes('.stripprefix.'),\n );\n const localBasePath = (traefikPath && !hasStripPrefix) ? traefikPath : '';\n\n // Compute external URL from Traefik PathPrefix labels on the container\n let externalUrl: string | null = null;\n const qtUrl = quickTunnelUrl;\n const tunnelMode = wizardState?.domain?.cloudflare?.tunnelMode ?? 'none';\n const namedDomain = wizardState?.domain?.cloudflare?.zoneName || wizardState?.domain?.name || '';\n\n if (tunnelMode === 'named' && namedDomain) {\n // Named Tunnel: built-in services use fixed subdomains\n const sub = NAMED_SUBDOMAIN_MAP[composeService];\n if (sub) {\n externalUrl = `https://${sub}.${namedDomain}`;\n }\n // User apps: look up in domainConnections by composeService name\n // (appName may match composeService directly, or be a brewnet- prefixed variant)\n if (!externalUrl) {\n const appNameVariants = [composeService, composeService.replace(/^brewnet-/, '')];\n const conn = (wizardState?.domainConnections ?? []).find(\n (c) => appNameVariants.includes(c.appName),\n );\n if (conn) externalUrl = `https://${conn.hostname}`;\n }\n } else if (qtUrl && traefikPath) {\n // Quick Tunnel: PathPrefix-based URL\n let extPath = traefikPath;\n // Unified API-only stacks (e.g. nextjs-app): append /api/hello\n // so the external URL points to the API endpoint, not the empty root\n const stackLabel = labels['com.brewnet.stack'] ?? '';\n if (stackLabel === 'nodejs-nextjs' || (composeService === 'backend' && extPath.includes('nextjs-app'))) {\n extPath += '/api/hello';\n }\n externalUrl = qtUrl.replace(/\\/$/, '') + extPath;\n }\n if (!externalUrl && qtUrl && tunnelMode !== 'named') {\n // Fallback for known homeserver services (EXT_PATHS)\n const EXT_PATH_MAP: Record<string, string> = {\n traefik: '', gitea: '/git', nextcloud: '/cloud', pgadmin: '/pgadmin',\n jellyfin: '/jellyfin', filebrowser: '/files', minio: '/minio',\n };\n if (EXT_PATH_MAP[composeService] !== undefined) {\n externalUrl = qtUrl.replace(/\\/$/, '') + EXT_PATH_MAP[composeService];\n }\n }\n\n // Extract stackId from working_dir relative to projectPath (e.g. \".../go-gin\" → \"go-gin\")\n const stackId = (workingDir && workingDir.startsWith(_projectPath))\n ? workingDir.slice(_projectPath.length).replace(/^[/\\\\]/, '') || undefined\n : undefined;\n\n // For generic boilerplate service names (frontend/backend), prefix with the\n // compose project name so cards read \"nodejs-express-front\" instead of just \"frontend\".\n const GENERIC_BOILERPLATE_SERVICES = new Set(['frontend', 'backend']);\n const composeProject = c.Labels?.['com.docker.compose.project'] ?? '';\n const isGeneric = GENERIC_BOILERPLATE_SERVICES.has(composeService) && !!composeProject;\n const serviceId = isGeneric ? `${composeProject}-${composeService}` : composeService;\n const serviceName = isGeneric\n ? `${composeProject}-${composeService === 'frontend' ? 'front' : 'back'}`\n : (def?.name ?? composeService);\n\n services.push({\n id: serviceId,\n name: serviceName,\n type: def ? inferType(composeService) : 'unknown',\n status,\n cpu: '—',\n memory: '—',\n uptime: c.Status?.startsWith('Up') ? c.Status.replace(/^Up /, '') : '—',\n port: port ?? null,\n // Show a local URL for any service with a public HTTP port.\n // Database/queue services (non-HTTP) are excluded via NO_HTTP_SERVICES.\n // urlMap overrides apply first (e.g. Traefik-path services like gitea → /git).\n // localBasePath: Next.js basePath stacks serve at /apps/{name} even locally.\n url: port && !NO_HTTP_SERVICES.has(composeService)\n ? urlMap[composeService] ?? `http://localhost:${port}${localBasePath}`\n : null,\n externalUrl,\n removable: !REQUIRED_SERVICES.has(composeService),\n stackId,\n });\n }\n\n const running = services.filter((s) => s.status === 'running').length;\n json(res, 200, {\n services,\n summary: { total: services.length, running, stopped: services.length - running },\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err), code: 'BN001' });\n }\n}\n\nfunction inferType(id: string): string {\n if (['traefik', 'nginx', 'caddy'].includes(id)) return 'web';\n if (['postgresql', 'mysql'].includes(id)) return 'db';\n if (['nextcloud', 'minio', 'filebrowser'].includes(id)) return 'file';\n if (['jellyfin'].includes(id)) return 'media';\n if (['gitea'].includes(id)) return 'git';\n if (['openssh-server'].includes(id)) return 'ssh';\n return 'app';\n}\n\nasync function handleServiceAction(\n _req: IncomingMessage,\n res: ServerResponse,\n parts: string[],\n _body: string,\n _projectPath: string,\n): Promise<void> {\n const serviceId = parts[3]; // /api/services/containers/:id/start|stop → parts[3]=id\n const action = parts[4] as 'start' | 'stop';\n\n if (!serviceId || !['start', 'stop'].includes(action)) {\n json(res, 400, { success: false, error: 'Invalid request' });\n return;\n }\n\n try {\n const containers = await docker.listContainers({ all: true });\n const match = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === serviceId,\n );\n\n if (!match) {\n json(res, 404, { success: false, error: 'Service not found', code: 'BN008' });\n return;\n }\n\n if (action === 'start' && match.State === 'running') {\n json(res, 400, { success: false, error: 'Service is already running', code: 'ALREADY_RUNNING' });\n return;\n }\n if (action === 'stop' && match.State !== 'running') {\n json(res, 400, { success: false, error: 'Service is not running', code: 'NOT_RUNNING' });\n return;\n }\n\n const container = docker.getContainer(match.Id);\n if (action === 'start') {\n await container.start();\n } else {\n await container.stop();\n }\n\n const newStatus = action === 'start' ? 'running' : 'stopped';\n json(res, 200, { success: true, id: serviceId, status: newStatus });\n } catch (err) {\n json(res, 500, { success: false, error: String(err), code: 'BN001' });\n }\n}\n\nasync function handleInstallService(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n body: string,\n projectPath: string,\n): Promise<void> {\n try {\n const { id } = JSON.parse(body) as { id: string };\n if (!id) { json(res, 400, { success: false, error: 'Missing service id' }); return; }\n\n const result = await addService(id, projectPath);\n if (result.success) {\n json(res, 202, { success: true, id, status: 'installed', message: `Service ${id} added` });\n } else {\n const code = result.error?.includes('already') ? 'ALREADY_EXISTS' : 'BN006';\n json(res, result.error?.includes('already') ? 409 : 500, { success: false, error: result.error, code });\n }\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleRemoveService(\n req: IncomingMessage,\n res: ServerResponse,\n parts: string[],\n _body: string,\n projectPath: string,\n): Promise<void> {\n const serviceId = parts[3]; // DELETE /api/services/containers/:id → parts[3]=id\n if (!serviceId) { json(res, 400, { success: false, error: 'Missing service id' }); return; }\n\n if (REQUIRED_SERVICES.has(serviceId)) {\n json(res, 400, { success: false, error: `Cannot remove required service: ${serviceId}`, code: 'REQUIRED_SERVICE' });\n return;\n }\n\n const url = new URL(req.url ?? '/', `http://localhost`);\n const purge = url.searchParams.get('purge') === 'true';\n\n try {\n const result = await removeService(serviceId, projectPath, { purge });\n if (result.success) {\n json(res, 200, { success: true, id: serviceId, dataPreserved: !purge });\n } else {\n json(res, result.error?.includes('not found') ? 404 : 500, { success: false, error: result.error, code: 'BN008' });\n }\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleGetCatalog(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n _projectPath: string,\n): Promise<void> {\n try {\n const installed = new Set<string>();\n const containers = await docker.listContainers({ all: true });\n for (const c of containers) {\n const id = c.Labels?.['com.docker.compose.service'];\n if (id) installed.add(id);\n }\n\n const catalog = [...SERVICE_REGISTRY.values()]\n .filter((def) => !REQUIRED_SERVICES.has(def.id))\n .map((def) => ({\n id: def.id,\n name: def.name,\n description: '',\n category: inferType(def.id),\n image: def.image,\n ramEstimateMB: def.ramMB,\n installed: installed.has(def.id),\n }));\n\n json(res, 200, { catalog });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleBackup(\n req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n projectPath: string,\n): Promise<void> {\n const backupsDir = join(homedir(), '.brewnet', 'backups');\n\n if (req.method === 'GET') {\n try {\n const backups = listBackups(backupsDir);\n json(res, 200, { backups });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n return;\n }\n\n // POST - create backup\n try {\n const record = createBackup(projectPath, backupsDir);\n json(res, 202, { success: true, backupId: record.id, status: 'completed' });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Per-app domain operation log buffer (feeds into SSE /logs stream)\n// ---------------------------------------------------------------------------\n\n/** In-memory ring buffer: appName → last 200 domain op log lines */\nconst domainOpLogs = new Map<string, Array<{ line: string; ts: number }>>();\n\n/** Live listeners: SSE clients currently tailing logs for an app */\nconst domainOpListeners = new Map<string, Set<(line: string) => void>>();\n\nfunction writeDomainLog(appName: string, line: string): void {\n const tagged = `[domain-connect] ${line}`;\n logger.info('domain', `[${appName}] ${line}`);\n if (!domainOpLogs.has(appName)) domainOpLogs.set(appName, []);\n const buf = domainOpLogs.get(appName)!;\n buf.push({ line: tagged, ts: Date.now() });\n if (buf.length > 200) buf.shift();\n domainOpListeners.get(appName)?.forEach((fn) => fn(tagged));\n}\n\n// ---------------------------------------------------------------------------\n// Server factory\n// ---------------------------------------------------------------------------\n\nexport function createAdminServer(options: AdminServerOptions = {}): {\n server: Server;\n start: () => Promise<number>;\n stop: () => Promise<void>;\n} {\n const port = options.port ?? 8088;\n\n // Resolve project path and wizard state.\n // Always load wizard state from the last project — options.projectPath only\n // overrides the filesystem path, not whether state is loaded.\n let projectPath = options.projectPath ?? process.cwd();\n let wizardState: WizardState | null = null;\n const last = getLastProject();\n if (last) {\n const state = loadState(last);\n if (state) {\n wizardState = state;\n // Only fall back to state.projectPath when caller didn't supply one\n if (!options.projectPath && state.projectPath) projectPath = state.projectPath;\n }\n }\n // Expand leading ~ — Node.js fs APIs do not interpret tilde as home directory\n if (projectPath.startsWith('~/') || projectPath === '~') {\n projectPath = join(homedir(), projectPath.slice(1));\n }\n\n // Build dashboard config from wizard state (credentials resolved lazily if needed)\n const username = wizardState?.admin?.username ?? '';\n const password = wizardState?.admin?.password ?? '';\n\n // Mask helpers\n const maskUser = (u: string) => (u.length > 2 ? u.slice(0, -2) + '**' : '**');\n const maskPass = (p: string) => (p.length > 1 ? p[0] + '*'.repeat(p.length - 1) : '********');\n\n // Build set of allowed compose working dirs for service filtering.\n // Only containers from these directories are shown in the Services table.\n // This excludes unselected boilerplate stacks (e.g. test deployments of all 16 stacks).\n const allowedWorkingDirs = new Set<string>();\n allowedWorkingDirs.add(projectPath); // homeserver services (traefik, gitea, etc.)\n try {\n // Selected boilerplate stacks from wizard\n const bpMetaPath2 = join(projectPath, '.brewnet-boilerplate.json');\n if (existsSync(bpMetaPath2)) {\n const raw2 = JSON.parse(readFileSync(bpMetaPath2, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const stacks2: BoilerplateMeta[] = Array.isArray(raw2) ? raw2 : (raw2.stackId ? [raw2] : []);\n for (const s of stacks2) {\n if (s.appDir) allowedWorkingDirs.add(s.appDir);\n }\n }\n // Apps registered via `brewnet deploy` (app-manager) — ~/.brewnet/apps.json\n const appsJsonPath = join(homedir(), '.brewnet', 'apps.json');\n if (existsSync(appsJsonPath)) {\n const apps = JSON.parse(readFileSync(appsJsonPath, 'utf-8')) as Array<{ appDir?: string }>;\n for (const app of apps) {\n if (app.appDir) allowedWorkingDirs.add(app.appDir);\n }\n }\n } catch { /* non-fatal */ }\n\n const dashConfig: DashboardConfig = {\n adminUsername: username ? maskUser(username) : '**',\n passwordHint: password ? maskPass(password) : '********',\n domainProvider: wizardState?.domain?.provider ?? 'local',\n quickTunnelUrl: wizardState?.domain?.cloudflare?.quickTunnelUrl ?? '',\n zoneName: wizardState?.domain?.cloudflare?.zoneName ?? '',\n tunnelId: wizardState?.domain?.cloudflare?.tunnelId ?? '',\n };\n\n // Compute runtime URL map — extends static TRAEFIK_PATH_SERVICES.\n // Jellyfin local URL always uses direct port 8096 (bypasses Traefik).\n // Reason: Traefik's catch-all landing page router returns HTTP 200 for any\n // unmapped path (including /System/Info/Public), which confuses Jellyfin SPA's\n // server auto-detection. Direct port access lets Jellyfin redirect unmapped\n // paths to ../../jellyfin/web/, giving the SPA a correct base URL hint.\n const runtimeUrlMap: Record<string, string> = {\n ...TRAEFIK_PATH_SERVICES,\n jellyfin: 'http://localhost:8096/jellyfin/web/',\n };\n\n let quickTunnelDetected = !!dashConfig.quickTunnelUrl;\n\n /**\n * Detect Quick Tunnel URL from running cloudflared container logs.\n * Called once on first request if no tunnel URL is in the config.\n */\n async function detectQuickTunnelUrl(): Promise<void> {\n if (quickTunnelDetected) return;\n quickTunnelDetected = true; // prevent repeated attempts\n try {\n const containers = await docker.listContainers({ all: true });\n const cf = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === 'cloudflared',\n );\n if (!cf || cf.State !== 'running') return;\n const container = docker.getContainer(cf.Id);\n const logBuf = await container.logs({ stdout: true, stderr: true, tail: 50 });\n const logStr = logBuf.toString('utf-8');\n const match = logStr.match(/https?:\\/\\/([\\w]+-[\\w][\\w-]*\\.trycloudflare\\.com)/i);\n if (match) {\n dashConfig.quickTunnelUrl = `https://${match[1]}`;\n dashConfig.domainProvider = 'quick-tunnel';\n }\n } catch {\n // Non-critical — just serve without external URLs\n }\n }\n\n /**\n * Lazy credential detection from running Nextcloud container env vars.\n * Called once on first dashboard request when wizard state is unavailable.\n */\n let credentialsDetected = !!(username && password);\n async function detectCredentials(): Promise<void> {\n if (credentialsDetected) return;\n credentialsDetected = true; // prevent repeated attempts\n try {\n const containers = await docker.listContainers({ all: true });\n const nc = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === 'nextcloud',\n );\n if (!nc) return;\n const info = await docker.getContainer(nc.Id).inspect();\n const envArr: string[] = info.Config?.Env ?? [];\n let u = '';\n let p = '';\n for (const entry of envArr) {\n if (!u && entry.startsWith('NEXTCLOUD_ADMIN_USER=')) {\n u = entry.split('=').slice(1).join('=');\n }\n if (!p && entry.startsWith('NEXTCLOUD_ADMIN_PASSWORD=')) {\n p = entry.split('=').slice(1).join('=');\n }\n }\n if (u || p) {\n dashConfig.adminUsername = maskUser(u || 'admin');\n dashConfig.passwordHint = maskPass(p);\n }\n } catch {\n // Non-critical — fall through to defaults\n }\n }\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = req.url ?? '/';\n const parts = url.split('?')[0].split('/').filter(Boolean);\n const body = await readBody(req);\n\n // CORS for dev convenience\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Admin-Password');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Serve Brewnet SVG icon\n if (req.method === 'GET' && url === '/icon.svg') {\n res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' });\n res.end(ICON_SVG);\n return;\n }\n\n // Serve favicon.ico (binary from disk; fallback: SVG with image/x-icon)\n if (req.method === 'GET' && url === '/favicon.ico') {\n if (FAVICON_ICO) {\n res.writeHead(200, { 'Content-Type': 'image/x-icon', 'Cache-Control': 'public, max-age=86400' });\n res.end(FAVICON_ICO);\n } else {\n res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' });\n res.end(ICON_SVG);\n }\n return;\n }\n\n // Serve React SPA static assets (/assets/*)\n if (req.method === 'GET' && url.startsWith('/assets/')) {\n const pathname = url.split('?')[0];\n const safePath = resolve(ADMIN_UI_DIST, '.' + pathname);\n if (!safePath.startsWith(ADMIN_UI_DIST)) {\n res.writeHead(403); res.end('Forbidden'); return;\n }\n if (existsSync(safePath) && statSync(safePath).isFile()) {\n serveStaticFile(safePath, res);\n return;\n }\n res.writeHead(404); res.end('Not Found');\n return;\n }\n\n // SPA fallback — serve index.html for all non-API GET requests\n if (req.method === 'GET' && !url.startsWith('/api/')) {\n const pathname = url.split('?')[0];\n // Exact static file (e.g. /vite.svg, /favicon.ico from dist/)\n const exactPath = resolve(ADMIN_UI_DIST, '.' + (pathname === '/' ? '/index.html' : pathname));\n if (exactPath.startsWith(ADMIN_UI_DIST) && existsSync(exactPath) && statSync(exactPath).isFile()) {\n serveStaticFile(exactPath, res);\n return;\n }\n // SPA fallback: serve index.html\n const indexPath = join(ADMIN_UI_DIST, 'index.html');\n if (existsSync(indexPath)) {\n serveStaticFile(indexPath, res);\n return;\n }\n // admin-ui not built yet\n res.writeHead(503, { 'Content-Type': 'text/plain' });\n res.end('Admin UI not built. Run: pnpm --filter @brewnet/admin-ui build');\n return;\n }\n\n\n // --- API routing ---\n if (parts[0] === 'api') {\n try {\n if (parts[1] === 'health' && req.method === 'GET') {\n // When a password is configured, validate it so PasswordGate can\n // use this endpoint as the real auth check.\n if (wizardState?.admin?.password) {\n const provided = req.headers['x-admin-password'] as string | undefined;\n if (!provided || provided !== wizardState.admin.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password required' });\n return;\n }\n }\n json(res, 200, { status: 'ok', version: '1.0.1' });\n return;\n }\n\n // GET /api/services/catalog — SERVICE_DETAIL_MAP + NAME_ALIASES for React SPA\n if (parts[1] === 'services' && parts[2] === 'catalog' && req.method === 'GET') {\n const adminUser = wizardState?.admin?.username ?? 'USER';\n const catalog = { ...SERVICE_DETAIL_MAP };\n if (catalog['SSH Server']) {\n catalog['SSH Server'] = {\n ...catalog['SSH Server'],\n credentials: {\n ...catalog['SSH Server'].credentials!,\n command: `ssh -p 2222 ${adminUser}@localhost`,\n },\n connectionParams: catalog['SSH Server'].connectionParams?.map((p) =>\n p.label === 'user' ? { ...p, value: adminUser } : p,\n ),\n };\n }\n json(res, 200, { catalog, aliases: NAME_ALIASES });\n return;\n }\n\n // GET /api/config — dashboard bootstrap data for React SPA\n if (parts[1] === 'config' && req.method === 'GET') {\n await detectQuickTunnelUrl();\n await detectCredentials();\n json(res, 200, {\n adminUsername: dashConfig.adminUsername,\n passwordHint: dashConfig.passwordHint,\n domainProvider: dashConfig.domainProvider,\n quickTunnelUrl: dashConfig.quickTunnelUrl,\n zoneName: dashConfig.zoneName,\n tunnelId: dashConfig.tunnelId,\n });\n return;\n }\n\n if (parts[1] === 'services') {\n if (req.method === 'GET' && parts.length === 2) {\n await handleGetServices(req, res, parts, body, projectPath, runtimeUrlMap, dashConfig.quickTunnelUrl, allowedWorkingDirs, wizardState);\n return;\n }\n if (req.method === 'POST' && parts[2] === 'install') {\n await handleInstallService(req, res, parts, body, projectPath);\n return;\n }\n // POST /api/services/containers/:id/start|stop → parts[3]=id, parts[4]=action\n if (req.method === 'POST' && parts[3] && ['start', 'stop'].includes(parts[4] ?? '')) {\n await handleServiceAction(req, res, parts, body, projectPath);\n return;\n }\n // DELETE /api/services/containers/:id → parts[3]=id\n if (req.method === 'DELETE' && parts[3]) {\n await handleRemoveService(req, res, parts, body, projectPath);\n return;\n }\n }\n\n if (parts[1] === 'catalog' && req.method === 'GET') {\n await handleGetCatalog(req, res, parts, body, projectPath);\n return;\n }\n\n if (parts[1] === 'backup') {\n await handleBackup(req, res, parts, body, projectPath);\n return;\n }\n\n // ── Logs API (T021-T022) ────────────────────────────────────\n if (parts[1] === 'logs') {\n if (req.method === 'GET' && parts[2] === 'stats') {\n const stats = await getLogStats(projectPath);\n json(res, 200, stats);\n return;\n }\n if (req.method === 'GET') {\n const qUrl = new URL(url, 'http://localhost');\n const sources = qUrl.searchParams.get('source');\n const levels = qUrl.searchParams.get('level');\n const services = qUrl.searchParams.get('service');\n const since = qUrl.searchParams.get('since') ?? undefined;\n const until = qUrl.searchParams.get('until') ?? undefined;\n const search = qUrl.searchParams.get('search') ?? undefined;\n const limit = parseInt(qUrl.searchParams.get('limit') ?? '100', 10);\n const offset = parseInt(qUrl.searchParams.get('offset') ?? '0', 10);\n\n const result = await queryLogs(\n {\n sources: sources ? [sources as LogSource] : undefined,\n levels: levels ? [levels as UnifiedLogLevel] : undefined,\n services: services ? [services] : undefined,\n since,\n until,\n search,\n limit: isNaN(limit) ? 100 : limit,\n offset: isNaN(offset) ? 0 : offset,\n },\n projectPath,\n );\n json(res, 200, result);\n return;\n }\n }\n\n if (parts[1] === 'apps') {\n if (req.method === 'GET' && parts.length === 2) {\n const apps = await listApps();\n // Enrich with lastDeployedAt + localUrl (with basePath for Next.js)\n const history = getDeployHistory();\n const historyByApp = new Map<string, typeof history[0]>();\n for (const h of history) { if (h.status === 'success') historyByApp.set(h.appName, h); }\n // Load boilerplate meta once for frontend URL lookup\n const bpMetaPath = join(projectPath, '.brewnet-boilerplate.json');\n const bpMetaMap = new Map<string, BoilerplateMeta>();\n if (existsSync(bpMetaPath)) {\n try {\n const raw = JSON.parse(readFileSync(bpMetaPath, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const metas: BoilerplateMeta[] = Array.isArray(raw) ? raw : [raw];\n for (const m of metas) bpMetaMap.set(m.stackId, m);\n } catch { /* ignore parse errors */ }\n }\n const enrichedApps = apps.map((a) => {\n const lastDeploy = historyByApp.get(a.name) ?? null;\n const qt = dashConfig.quickTunnelUrl;\n // Non-unified: bpMeta says so, or fall back to stack catalog (for create-app apps)\n const bpMeta = a.mode === 'boilerplate' && a.stackId ? bpMetaMap.get(a.stackId) : undefined;\n const isNonUnified = bpMeta\n ? bpMeta.isUnified === false\n : !!(a.stackId && getStackById(a.stackId)?.isUnified === false);\n let localUrl: string | null;\n let externalUrl: string | null;\n let backendLocalUrl: string | null = null;\n let backendExternalUrl: string | null = null;\n // Check domainConnections for this app (Named Tunnel or any connected domain)\n const domainConn = (wizardState?.domainConnections ?? []).find((c) => c.appName === a.name);\n if (isNonUnified) {\n // Read actual FRONTEND_PORT from .env — meta may have stale default (3000)\n let frontendPort = 3000;\n const feEnvPath = join(a.appDir, '.env');\n if (existsSync(feEnvPath)) {\n const feEnvContent = readFileSync(feEnvPath, 'utf-8');\n const fePortMatch = feEnvContent.match(/^FRONTEND_PORT=(\\d+)/m);\n if (fePortMatch) frontendPort = parseInt(fePortMatch[1], 10);\n }\n localUrl = `http://127.0.0.1:${frontendPort}`;\n externalUrl = domainConn ? `https://${domainConn.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}-ui` : null);\n backendLocalUrl = a.port ? `http://127.0.0.1:${a.port}` : null;\n backendExternalUrl = domainConn ? `https://${domainConn.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}` : null);\n } else {\n // Compute localUrl with basePath (same logic as Dashboard services)\n localUrl = a.port ? `http://localhost:${a.port}` : null;\n if (a.appDir) {\n const bp = detectBasePath(a.appDir);\n if (bp && localUrl) localUrl += bp;\n }\n externalUrl = domainConn ? `https://${domainConn.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}` : null);\n }\n return { ...a, lastDeployedAt: lastDeploy?.deployedAt ?? null, localUrl, externalUrl, backendLocalUrl, backendExternalUrl };\n });\n logger.info('admin-server', `[GET /api/apps] returning ${apps.length} app(s): ${JSON.stringify(apps.map((a) => a.name))}`);\n json(res, 200, { apps: enrichedApps });\n return;\n }\n if (req.method === 'GET' && parts[2] === 'boilerplates') {\n const bpPath = join(projectPath, '.brewnet-boilerplate.json');\n const metas: BoilerplateMeta[] = [];\n // Wizard-era boilerplates (from .brewnet-boilerplate.json)\n if (existsSync(bpPath)) {\n const raw = JSON.parse(readFileSync(bpPath, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const wizardMetas = Array.isArray(raw) ? raw : [raw];\n metas.push(...wizardMetas);\n }\n // create-app boilerplate apps (from apps.json)\n const allApps = await listApps();\n for (const app of allApps) {\n if (app.mode !== 'boilerplate' || !app.stackId) continue;\n // Skip if already covered by wizard .brewnet-boilerplate.json (same appDir)\n if (metas.some((m) => m.appDir && app.appDir && m.appDir === app.appDir)) continue;\n const envPath = join(app.appDir, '.env');\n let frontendPort: number | undefined;\n let dbDriver: string | undefined;\n let dbUser: string | undefined;\n let dbName: string | undefined;\n if (existsSync(envPath)) {\n const envContent = readFileSync(envPath, 'utf-8');\n const fpMatch = envContent.match(/^FRONTEND_PORT=(\\d+)/m);\n if (fpMatch) frontendPort = parseInt(fpMatch[1]!, 10);\n const ddMatch = envContent.match(/^DB_DRIVER=(.+)/m);\n if (ddMatch) dbDriver = ddMatch[1]!.trim();\n const duMatch = envContent.match(/^DB_USER=(.+)/m);\n if (duMatch) dbUser = duMatch[1]!.trim();\n const dnMatch = envContent.match(/^DB_NAME=(.+)/m);\n if (dnMatch) dbName = dnMatch[1]!.trim();\n }\n const stackEntry = getStackById(app.stackId);\n metas.push({\n stackId: app.stackId,\n appDir: app.appDir,\n backendUrl: `http://127.0.0.1:${app.port}`,\n frontendUrl: frontendPort ? `http://127.0.0.1:${frontendPort}` : undefined,\n isUnified: stackEntry?.isUnified ?? false,\n lang: app.lang,\n dbDriver,\n dbUser,\n dbName,\n status: app.status,\n });\n }\n logger.info('admin-server', `[GET /api/apps/boilerplates] returning ${metas.length} boilerplate(s) (wizard=${metas.length - allApps.filter((a) => a.mode === 'boilerplate' && a.stackId).length}, create-app=${allApps.filter((a) => a.mode === 'boilerplate' && a.stackId).length})`);\n json(res, 200, { boilerplates: metas });\n return;\n }\n // POST /api/apps/boilerplates/:stackId/stop — docker compose down\n // POST /api/apps/boilerplates/:stackId/start — docker compose up -d\n if (req.method === 'POST' && parts[2] === 'boilerplates' && parts[3] && (parts[4] === 'stop' || parts[4] === 'start')) {\n const stackId = decodeURIComponent(parts[3]);\n const action = parts[4] as 'stop' | 'start';\n const bpPath = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(bpPath)) { json(res, 404, { error: 'No boilerplates found' }); return; }\n const bpRaw = JSON.parse(readFileSync(bpPath, 'utf-8'));\n const bpMetas: BoilerplateMeta[] = Array.isArray(bpRaw) ? bpRaw : [bpRaw];\n const meta = bpMetas.find((m) => m.stackId === stackId);\n if (!meta) { json(res, 404, { error: `Boilerplate \"${stackId}\" not found` }); return; }\n const { execa: execaBp } = await import('execa');\n if (action === 'stop') {\n await execaBp('docker', ['compose', 'down'], { cwd: meta.appDir });\n meta.status = 'stopped';\n } else {\n await execaBp('docker', ['compose', 'up', '-d'], { cwd: meta.appDir });\n meta.status = 'running';\n }\n // Persist status update back to .brewnet-boilerplate.json\n const { writeFileSync } = await import('node:fs');\n writeFileSync(bpPath, JSON.stringify(bpMetas, null, 2), 'utf-8');\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'POST' && parts[2] === 'create') {\n const opts = JSON.parse(body) as CreateAppOptions;\n const jobId = await createApp(opts);\n json(res, 202, { jobId });\n return;\n }\n if (req.method === 'GET' && parts[2] === 'jobs' && parts[3]) {\n const job = getJobStatus(parts[3]);\n if (!job) { json(res, 404, { error: 'Job not found' }); return; }\n json(res, 200, job as unknown as Record<string, unknown>);\n return;\n }\n if (req.method === 'POST' && parts[3] === 'start') {\n await startApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'POST' && parts[3] === 'stop') {\n await stopApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'DELETE' && parts[2]) {\n await appRemove(parts[2]);\n json(res, 200, { success: true });\n return;\n }\n\n // GET /api/apps/:name — single app detail (enriched with lastDeployedAt + URLs)\n if (req.method === 'GET' && parts[2] && !['boilerplates', 'jobs', 'check-port'].includes(parts[2]) && parts.length === 3) {\n const apps = await listApps();\n const found = apps.find((a) => a.name === decodeURIComponent(parts[2]!));\n if (!found) { json(res, 404, { error: 'App not found' }); return; }\n const history = getDeployHistory();\n const lastDeploy = history.filter((h) => h.appName === found.name && h.status === 'success').pop() ?? null;\n const qt = dashConfig.quickTunnelUrl;\n const bpMetaSingle = found.mode === 'boilerplate' && found.stackId\n ? (() => {\n const p = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(p)) return undefined;\n try {\n const raw = JSON.parse(readFileSync(p, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const list = Array.isArray(raw) ? raw : [raw];\n return list.find((m) => m.stackId === found.stackId);\n } catch { return undefined; }\n })()\n : undefined;\n // Non-unified: bpMeta says so, or fall back to stack catalog (for create-app apps)\n const isNonUnified = bpMetaSingle\n ? bpMetaSingle.isUnified === false\n : !!(found.stackId && getStackById(found.stackId)?.isUnified === false);\n let localUrlSingle: string | null;\n let externalUrlSingle: string | null;\n let backendLocalUrlSingle: string | null = null;\n let backendExternalUrlSingle: string | null = null;\n const domainConnSingle = (wizardState?.domainConnections ?? []).find((c) => c.appName === found.name);\n if (isNonUnified) {\n let frontendPort = 3000;\n const feEnvPath = join(found.appDir, '.env');\n if (existsSync(feEnvPath)) {\n const m = readFileSync(feEnvPath, 'utf-8').match(/^FRONTEND_PORT=(\\d+)/m);\n if (m) frontendPort = parseInt(m[1], 10);\n }\n localUrlSingle = `http://127.0.0.1:${frontendPort}`;\n externalUrlSingle = domainConnSingle ? `https://${domainConnSingle.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}-ui` : null);\n backendLocalUrlSingle = found.port ? `http://127.0.0.1:${found.port}` : null;\n backendExternalUrlSingle = domainConnSingle ? `https://${domainConnSingle.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}` : null);\n } else {\n localUrlSingle = found.port ? `http://localhost:${found.port}` : null;\n if (found.appDir) {\n const bp = detectBasePath(found.appDir);\n if (bp && localUrlSingle) localUrlSingle += bp;\n }\n externalUrlSingle = domainConnSingle ? `https://${domainConnSingle.hostname}` : (qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}` : null);\n }\n const app = { ...found, lastDeployedAt: lastDeploy?.deployedAt ?? null, localUrl: localUrlSingle, externalUrl: externalUrlSingle, backendLocalUrl: backendLocalUrlSingle, backendExternalUrl: backendExternalUrlSingle };\n json(res, 200, { app });\n return;\n }\n\n // GET /api/apps/:name/git\n if (req.method === 'GET' && parts[3] === 'git' && parts.length === 4) {\n try {\n const git = await getAppGitInfo(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { git });\n } catch (err) {\n json(res, 502, { error: String(err) });\n }\n return;\n }\n\n // GET /api/apps/:name/branches\n if (req.method === 'GET' && parts[3] === 'branches' && parts.length === 4) {\n try {\n const branches = await getAppBranches(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { branches });\n } catch (_err) {\n json(res, 200, { branches: [] });\n }\n return;\n }\n\n // GET /api/apps/:name/deploy/settings\n if (req.method === 'GET' && parts[3] === 'deploy' && parts[4] === 'settings') {\n const settings = getDeploySettings(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, settings);\n return;\n }\n\n // PUT /api/apps/:name/deploy/settings\n if (req.method === 'PUT' && parts[3] === 'deploy' && parts[4] === 'settings') {\n const opts = JSON.parse(body) as Partial<DeploySettings>;\n updateDeploySettings(decodeURIComponent(parts[2] ?? ''), opts);\n json(res, 200, { success: true });\n return;\n }\n\n // POST /api/apps/:name/deploy — manual deploy trigger\n if (req.method === 'POST' && parts[3] === 'deploy' && !parts[4]) {\n const jobId = await deployApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 202, { jobId });\n return;\n }\n\n // POST /api/apps/:name/rollback — rollback to a specific commit\n if (req.method === 'POST' && parts[3] === 'rollback' && !parts[4]) {\n let parsed: { commitHash?: string } = {};\n try { parsed = JSON.parse(body); } catch { json(res, 400, { error: 'Invalid JSON' }); return; }\n if (!parsed.commitHash) { json(res, 400, { error: 'commitHash is required' }); return; }\n const jobId = await rollbackApp(decodeURIComponent(parts[2] ?? ''), parsed.commitHash);\n json(res, 202, { jobId });\n return;\n }\n\n // GET /api/apps/:name/logs — SSE stream (auth: X-Admin-Password header or ?token query)\n if (req.method === 'GET' && parts[3] === 'logs') {\n const appDir = getAppDir(decodeURIComponent(parts[2] ?? ''));\n if (!appDir) { json(res, 404, { error: 'App not found' }); return; }\n // SSE: EventSource cannot send custom headers so this endpoint is\n // intentionally unauthenticated (log data is read-only, non-sensitive).\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n // Flush headers immediately so EventSource fires 'open' even before\n // docker compose logs produces its first output line.\n res.flushHeaders();\n const { execa: execaLocal } = await import('execa');\n const proc = execaLocal('docker', ['compose', 'logs', '--follow', '--tail', '50'], {\n cwd: appDir,\n reject: false,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n const sendLine = (line: string) => {\n if (line.trim()) res.write(`data: ${line.replace(/\\r?\\n/g, ' ')}\\n\\n`);\n };\n proc.stdout?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n')) sendLine(line);\n });\n proc.stderr?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n')) sendLine(line);\n });\n\n // Also stream domain operation logs (domain connect/disconnect events)\n const sseAppName = decodeURIComponent(parts[2] ?? '');\n const domainListener = (line: string) => sendLine(line);\n if (!domainOpListeners.has(sseAppName)) domainOpListeners.set(sseAppName, new Set());\n domainOpListeners.get(sseAppName)!.add(domainListener);\n // Flush recent domain op logs on connect\n for (const entry of domainOpLogs.get(sseAppName) ?? []) sendLine(entry.line);\n\n req.on('close', () => {\n try { proc.kill(); } catch { /* ignore */ }\n domainOpListeners.get(sseAppName)?.delete(domainListener);\n });\n return;\n }\n }\n\n // ── Gitea auto-login proxy ──────────────────────────────────\n // GET /api/gitea/autologin?redirect=<gitea-internal-path>\n // Server-side: log into Gitea with admin creds, forward session cookie,\n // redirect browser to the target Gitea page — no plaintext creds in client.\n if (parts[1] === 'gitea' && parts[2] === 'autologin' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const redirectPath = reqUrl.searchParams.get('redirect') ?? '/git';\n const giteaBase = 'http://localhost/git';\n const targetUrl = `http://localhost${redirectPath.startsWith('/') ? redirectPath : '/' + redirectPath}`;\n try {\n // Step 1: GET login page to obtain CSRF cookie + form token\n const loginPageRes = await fetch(`${giteaBase}/user/login`, {\n redirect: 'manual',\n headers: { 'User-Agent': 'brewnet-admin' },\n signal: AbortSignal.timeout(5000),\n });\n const rawSetCookies: string[] = [];\n loginPageRes.headers.forEach((val, key) => {\n if (key.toLowerCase() === 'set-cookie') rawSetCookies.push(val);\n });\n const csrfCookieFull = rawSetCookies.find((c) => c.startsWith('_csrf=')) ?? '';\n const csrfCookieVal = csrfCookieFull.split(';')[0] ?? '';\n const pageHtml = await loginPageRes.text();\n const csrfField = pageHtml.match(/name=\"_csrf\"\\s+value=\"([^\"]+)\"/)?.[1]\n ?? pageHtml.match(/value=\"([^\"]+)\"\\s+name=\"_csrf\"/)?.[1]\n ?? csrfCookieVal.replace('_csrf=', '');\n if (!csrfField) {\n // CSRF unavailable — redirect without login (Gitea will show login page)\n res.writeHead(302, { Location: targetUrl });\n res.end();\n return;\n }\n // Step 2: POST login form with admin credentials\n const formBody = new URLSearchParams({\n _csrf: csrfField,\n user_name: username,\n password: password,\n remember: 'on',\n });\n const loginRes = await fetch(`${giteaBase}/user/login`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Cookie': csrfCookieVal,\n 'User-Agent': 'brewnet-admin',\n },\n body: formBody.toString(),\n redirect: 'manual',\n signal: AbortSignal.timeout(5000),\n });\n // Step 3: Extract ALL Gitea cookies from login response and forward them.\n // Gitea sets cookies with Path=/git (matching its ROOT_URL sub-path).\n // We must preserve the original Path so the browser sends them to Gitea.\n const respCookies: string[] = [];\n loginRes.headers.forEach((val, key) => {\n if (key.toLowerCase() === 'set-cookie') respCookies.push(val);\n });\n // Forward all non-empty, non-delete cookies (skip Max-Age=0 deletion entries)\n const forwardCookies = respCookies.filter((c) => !c.includes('Max-Age=0') && !c.match(/=;\\s/));\n const responseHeaders: Record<string, string | string[]> = { Location: targetUrl };\n if (forwardCookies.length > 0) {\n responseHeaders['Set-Cookie'] = forwardCookies;\n } else {\n logger.warn('admin-server', '[gitea/autologin] login POST did not return session cookies');\n }\n res.writeHead(302, responseHeaders);\n res.end();\n } catch (err) {\n logger.warn('admin-server', `[gitea/autologin] failed: ${String(err)}`);\n res.writeHead(302, { Location: targetUrl });\n res.end();\n }\n return;\n }\n\n // ── Deploy history, Git repos & Webhook ────────────────────\n if (parts[1] === 'deploy' && parts[2] === 'history' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const appFilter = reqUrl.searchParams.get('app') ?? undefined;\n const entries = getDeployHistory(appFilter);\n json(res, 200, { history: entries });\n return;\n }\n\n if (parts[1] === 'git' && parts[2] === 'repos' && req.method === 'GET') {\n try {\n const repos = await listGiteaRepos();\n const appsForEnrich = await listApps();\n // Enrich repos: map Gitea field names + attach connected appName\n const enriched = repos.map((repo) => {\n const r = repo as unknown as Record<string, unknown>;\n const connectedApp = appsForEnrich.find((app) =>\n app.giteaRepoUrl && (\n app.giteaRepoUrl.endsWith('/' + repo.name) ||\n app.giteaRepoUrl.includes('/' + repo.name + '.')\n ),\n );\n return {\n ...repo,\n language: (r['language'] as string | undefined) ?? '',\n stars: (r['stars_count'] as number | undefined) ?? 0,\n updatedAt: (r['updated'] as string | undefined) ?? '',\n appName: connectedApp?.name,\n };\n });\n json(res, 200, { repos: enriched });\n } catch (err) {\n json(res, 502, { success: false, error: String(err) });\n }\n return;\n }\n\n // POST /api/git/repos/:name/connect — Associate repo with an app\n if (parts[1] === 'git' && parts[2] === 'repos' && parts[3] && parts[4] === 'connect' && req.method === 'POST') {\n const repoName = decodeURIComponent(parts[3]);\n let parsed: { appName?: string } = {};\n try { parsed = JSON.parse(body); } catch { json(res, 400, { error: 'Invalid JSON' }); return; }\n const appName = parsed.appName?.trim();\n if (!appName) { json(res, 400, { error: 'appName required' }); return; }\n try {\n const appsPath = join(homedir(), '.brewnet', 'apps.json');\n let existing = existsSync(appsPath) ? JSON.parse(readFileSync(appsPath, 'utf-8')) : [];\n const repos = await listGiteaRepos();\n const repo = repos.find(r => r.name === repoName);\n if (!repo) { json(res, 404, { error: `Repo '${repoName}' not found in Gitea` }); return; }\n const repoUrl = repo.clone_url.replace(/\\.git$/, '');\n // Check not already connected to a different app\n const conflict = Array.isArray(existing) ? existing.find((a: { name: string; giteaRepoUrl?: string }) =>\n a.name !== appName && a.giteaRepoUrl && (a.giteaRepoUrl.endsWith('/' + repoName) || a.giteaRepoUrl.includes('/' + repoName + '.'))) : null;\n if (conflict) { json(res, 409, { error: `Repo already connected to app '${conflict.name}'` }); return; }\n let app = Array.isArray(existing) ? existing.find((a: { name: string }) => a.name === appName) : null;\n if (!app) {\n // Auto-create app entry for orphaned containers (e.g. wizard boilerplate\n // where health check failed but Gitea repo + Docker containers exist)\n const docker = new (await import('dockerode')).default();\n const containers = await docker.listContainers({ all: true });\n const matchedContainer = containers.find((c) => {\n const svc = c.Labels?.['com.docker.compose.service'] ?? '';\n const proj = c.Labels?.['com.docker.compose.project'] ?? '';\n return proj.includes(repoName) || proj.includes(appName) || svc === appName;\n });\n const port = matchedContainer\n ? parseInt(String(matchedContainer.Ports?.[0]?.PublicPort ?? 0), 10) || 8080\n : 8080;\n const lang = (repo as unknown as Record<string, unknown>)['language'] as string || '';\n app = {\n name: appName,\n mode: 'boilerplate' as const,\n appDir: join(projectPath, repoName),\n lang,\n port,\n giteaRepoUrl: repoUrl,\n status: matchedContainer?.State === 'running' ? 'running' : 'stopped',\n createdAt: new Date().toISOString(),\n };\n if (Array.isArray(existing)) { existing.push(app); } else { existing = [app]; }\n } else {\n app.giteaRepoUrl = repoUrl;\n }\n writeFileSync(appsPath, JSON.stringify(existing, null, 2));\n json(res, 200, { ok: true });\n } catch (err) {\n json(res, 500, { error: String(err) });\n }\n return;\n }\n\n // GET /api/apps/check-port?port=N — Check if a local port is available\n if (parts[1] === 'apps' && parts[2] === 'check-port' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const portStr = reqUrl.searchParams.get('port') ?? '';\n const port = parseInt(portStr, 10);\n if (!port || port < 1 || port > 65535) {\n json(res, 400, { error: 'Invalid port' });\n return;\n }\n const available = await new Promise<boolean>(resolve => {\n const sock = createConnection({ port, host: '127.0.0.1' });\n sock.once('connect', () => { sock.destroy(); resolve(false); });\n sock.once('error', () => resolve(true));\n sock.setTimeout(400, () => { sock.destroy(); resolve(true); });\n });\n json(res, 200, { port, available });\n return;\n }\n\n // POST /api/deploy/hook — Gitea push webhook for auto-deploy\n if (parts[1] === 'deploy' && parts[2] === 'hook' && req.method === 'POST') {\n try {\n const payload = JSON.parse(body) as {\n repository?: { name?: string };\n ref?: string;\n };\n const appName = payload.repository?.name;\n const branch = (payload.ref ?? '').replace('refs/heads/', '');\n if (appName) {\n const settings = getDeploySettings(appName);\n if (settings.autoDeploy && branch === settings.deployBranch) {\n void deployApp(appName);\n }\n }\n } catch { /* ignore parse errors */ }\n json(res, 200, { status: 'accepted' }); // always 200 to Gitea\n return;\n }\n\n // ── Domain API (T031-T036) ──────────────────────────────────\n if (parts[1] === 'domain') {\n // GET /api/domain/list — read-only, no auth needed (local admin server)\n if (req.method === 'GET' && parts[2] === 'list') {\n await handleDomainList(res, wizardState);\n return;\n }\n if (req.method === 'GET' && parts[2] === 'apps') {\n handleDomainApps(res, wizardState);\n return;\n }\n // POST /api/domain/connect — no auth needed (apps-page uses this; server is localhost-only)\n if (req.method === 'POST' && parts[2] === 'connect') {\n await handleDomainConnect(res, body, wizardState);\n return;\n }\n // Mutating operations that remain auth-gated\n if (!checkAdminAuth(req, res, wizardState)) return;\n if (req.method === 'DELETE' && parts[2] === 'disconnect' && parts[3]) {\n await handleDomainDisconnect(res, parts[3], wizardState);\n return;\n }\n if (req.method === 'GET' && parts[2] === 'status' && parts[3]) {\n await handleDomainStatus(res, parts[3], wizardState);\n return;\n }\n }\n\n // ── Cloudflare API (006-domain-settings) ───────────────────\n if (parts[1] === 'cloudflare') {\n if (!checkAdminAuth(req, res, wizardState)) return;\n if (req.method === 'GET' && parts[2] === 'zones') {\n await handleCloudflareZones(res, wizardState);\n return;\n }\n if (req.method === 'POST' && parts[2] === 'tunnel') {\n await handleCreateTunnel(res, body, wizardState, projectPath);\n return;\n }\n }\n\n // ── Settings API (T037-T038) ────────────────────────────────\n if (parts[1] === 'settings') {\n if (!checkAdminAuth(req, res, wizardState)) return;\n\n if (req.method === 'GET' && parts[2] === 'cloudflare') {\n handleSettingsCloudflareGet(res, wizardState);\n return;\n }\n if (req.method === 'PUT' && parts[2] === 'cloudflare') {\n await handleSettingsCloudflarePut(res, body, wizardState);\n return;\n }\n }\n\n json(res, 404, { success: false, error: 'Not found' });\n } catch (err) {\n logger.error('admin-server', 'Unhandled error', { error: String(err) });\n json(res, 500, { success: false, error: 'Internal server error' });\n }\n return;\n }\n\n res.writeHead(404);\n res.end('Not found');\n });\n\n return {\n server,\n start: () =>\n new Promise((resolve, reject) => {\n server.listen(port, '127.0.0.1', () => {\n logger.info('admin-server', `Listening on http://localhost:${port}`, { port });\n resolve(port);\n });\n server.once('error', reject);\n }),\n stop: () =>\n new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Admin password middleware (T031)\n// ---------------------------------------------------------------------------\n\nfunction checkAdminAuth(\n req: IncomingMessage,\n res: ServerResponse,\n state: WizardState | null,\n): boolean {\n if (!state?.admin?.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password not configured' });\n return false;\n }\n const provided = req.headers['x-admin-password'] as string | undefined;\n if (!provided || provided !== state.admin.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password required for this operation' });\n return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Domain API handlers (T032-T036)\n// ---------------------------------------------------------------------------\n\nasync function handleDomainList(\n res: ServerResponse,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 200, { connections: [], tunnel: null, credentialsConfigured: false });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const connections = mgr.list().map((c) => ({\n ...c,\n externalUrl: `https://${c.hostname}`,\n }));\n\n let tunnel = null;\n const cf = state.domain.cloudflare;\n if (cf.tunnelId && cf.apiToken && cf.accountId) {\n try {\n const { getTunnelHealth } = await import('./cloudflare-client.js');\n const health = await getTunnelHealth(cf.apiToken, cf.accountId, cf.tunnelId);\n tunnel = { ...health, tunnelName: cf.tunnelName, tunnelId: cf.tunnelId };\n } catch { /* leave null */ }\n }\n\n const credentialsConfigured = !!(cf.apiToken && cf.accountId && cf.zoneId && cf.tunnelId);\n\n json(res, 200, { connections, tunnel, credentialsConfigured });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nfunction handleDomainApps(\n res: ServerResponse,\n state: WizardState | null,\n): void {\n if (!state) {\n json(res, 200, { apps: [] });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const apps = mgr.getConnectableApps();\n json(res, 200, { apps });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainConnect(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n let parsed: { appName?: string; subdomain?: string; domain?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { appName, subdomain, domain } = parsed;\n if (!appName || !subdomain || !domain) {\n json(res, 400, { success: false, error: 'MISSING_FIELDS', message: 'appName, subdomain, and domain are required' });\n return;\n }\n\n // Validate subdomain format\n if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(subdomain)) {\n json(res, 400, { success: false, error: 'INVALID_SUBDOMAIN', message: 'Subdomain must be a valid DNS label' });\n return;\n }\n\n // Phase 1: local conflict check — fast, no API call needed\n const localConflict = (state.domainConnections ?? []).find(\n (c) => c.subdomain === subdomain && c.domain === domain && c.appName !== appName,\n );\n if (localConflict) {\n json(res, 409, {\n success: false,\n error: 'SUBDOMAIN_CONFLICT_LOCAL',\n message: `Subdomain \"${subdomain}.${domain}\" is already connected to app \"${localConflict.appName}\"`,\n conflictingApp: localConflict.appName,\n });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const result = await mgr.connect(appName, subdomain, domain, {\n onLog: (line) => writeDomainLog(appName, line.replace('[domain-connect] ', '')),\n });\n\n if (!result.success) {\n // Map CNAME_CONFLICT (from Cloudflare DNS check) to SUBDOMAIN_CONFLICT_EXTERNAL\n if (result.error === 'CNAME_CONFLICT') {\n json(res, 409, {\n success: false,\n error: 'SUBDOMAIN_CONFLICT_EXTERNAL',\n message: `Subdomain \"${subdomain}.${domain}\" already has a DNS record in Cloudflare (not created by Brewnet)`,\n steps: result.steps,\n });\n return;\n }\n const statusCode = result.error?.startsWith('APP_NOT_RUNNING') ? 503 : 400;\n json(res, statusCode, { success: false, error: result.error, message: result.error, steps: result.steps });\n return;\n }\n\n // Refresh in-memory wizardState so GET /api/apps sees updated domainConnections immediately\n const freshStateAfterConnect = loadState(state.projectName);\n if (freshStateAfterConnect?.domainConnections) {\n state.domainConnections = freshStateAfterConnect.domainConnections;\n }\n\n json(res, 200, {\n success: true,\n hostname: result.hostname,\n externalUrl: result.externalUrl,\n steps: result.steps,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainDisconnect(\n res: ServerResponse,\n appName: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const result = await mgr.disconnect(appName);\n\n if (!result.success) {\n const statusCode = result.error?.startsWith('NOT_CONNECTED') ? 404 : 500;\n json(res, statusCode, { success: false, error: result.error?.split(':')[0], message: result.error });\n return;\n }\n\n // Refresh in-memory wizardState so GET /api/apps reflects the removed connection immediately\n const freshStateAfterDisconnect = loadState(state.projectName);\n if (freshStateAfterDisconnect) {\n state.domainConnections = freshStateAfterDisconnect.domainConnections ?? [];\n }\n\n json(res, 200, {\n success: true,\n appName: result.appName,\n removedHostname: result.removedHostname,\n steps: result.steps,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainStatus(\n res: ServerResponse,\n appName: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 404, { success: false, error: 'No project state' });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const statuses = await mgr.status(appName);\n\n if (statuses.length === 0) {\n json(res, 404, { success: false, error: 'NOT_CONNECTED', message: `No domain connection for app: ${appName}` });\n return;\n }\n\n json(res, 200, statuses[0]);\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API handlers (006-domain-settings)\n// ---------------------------------------------------------------------------\n\nasync function handleCloudflareZones(\n res: ServerResponse,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 400, { success: false, error: 'NO_TOKEN', message: 'Cloudflare API token not configured. Complete Step 1 first.' });\n return;\n }\n\n const apiToken = state.domain.cloudflare.apiToken;\n if (!apiToken) {\n json(res, 400, { success: false, error: 'NO_TOKEN', message: 'Cloudflare API token not configured. Complete Step 1 first.' });\n return;\n }\n\n try {\n const { getZones } = await import('./cloudflare-client.js');\n const zones = await getZones(apiToken);\n\n // Extract accountId from the first zone (requires only Zone:Read).\n // This ensures accountId is set before tunnel creation even when\n // Account:Read permission is absent and getAccounts() returns [].\n if (!state.domain.cloudflare.accountId && zones.length > 0) {\n const firstAccountId = zones[0]?.accountId;\n if (firstAccountId) {\n state.domain.cloudflare.accountId = firstAccountId;\n const { saveState: save } = await import('../wizard/state.js');\n save(state);\n }\n }\n\n if (zones.length === 0) {\n json(res, 200, {\n success: true,\n zones: [],\n warning: 'No domains found. Ensure the token has Zone:Read permission and at least one domain is registered in your Cloudflare account.',\n });\n return;\n }\n\n json(res, 200, { success: true, zones });\n } catch (_err) {\n json(res, 400, {\n success: false,\n error: 'TOKEN_INVALID',\n message: 'Stored API token is no longer valid. Please re-enter your token.',\n });\n }\n}\n\nasync function handleCreateTunnel(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n projectPath: string,\n): Promise<void> {\n if (!state) {\n json(res, 400, { success: false, error: 'CREDENTIALS_INCOMPLETE', message: 'API token and zone must be configured before creating a tunnel.' });\n return;\n }\n\n let parsed: { tunnelName?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { tunnelName } = parsed;\n if (!tunnelName || !tunnelName.trim()) {\n json(res, 400, { success: false, error: 'MISSING_TUNNEL_NAME', message: 'tunnelName is required' });\n return;\n }\n\n const cf = state.domain.cloudflare;\n if (!cf.apiToken || !cf.accountId || !cf.zoneId) {\n json(res, 400, { success: false, error: 'CREDENTIALS_INCOMPLETE', message: 'API token and zone must be configured before creating a tunnel.' });\n return;\n }\n\n try {\n const { createTunnel: cfCreateTunnel } = await import('./cloudflare-client.js');\n const result = await cfCreateTunnel(cf.apiToken, cf.accountId, tunnelName.trim());\n\n const { saveState: save } = await import('../wizard/state.js');\n state.domain.cloudflare.tunnelId = result.tunnelId;\n state.domain.cloudflare.tunnelToken = result.tunnelToken;\n state.domain.cloudflare.tunnelName = tunnelName.trim();\n state.domain.cloudflare.tunnelMode = 'named';\n state.domain.cloudflare.enabled = true;\n save(state);\n\n // Also update state.domain.name to the zone name so Named Tunnel subdomain\n // URLs (git.<zone>, cloud.<zone>, etc.) are derived from the correct base domain.\n if (cf.zoneName) {\n state.domain.name = cf.zoneName;\n save(state);\n }\n\n const zoneName = cf.zoneName;\n\n // Normalize gitea-config.json to local URL — admin server always uses Traefik proxy,\n // not the external Named Tunnel URL which is unavailable until tunnel is fully active.\n try {\n const { saveGiteaConfig } = await import('./app-manager.js');\n const adminUsername = (state.admin as { username?: string } | undefined)?.username ?? 'admin';\n saveGiteaConfig('http://localhost/git', adminUsername);\n logger.info('tunnel', `[${tunnelName}] gitea-config.json normalized → http://localhost/git`);\n } catch (e) {\n logger.warn('tunnel', `[${tunnelName}] gitea-config.json update failed: ${e instanceof Error ? e.message : e}`);\n }\n\n const steps: Array<{ step: string; success: boolean; detail?: string; services?: string[] }> = [];\n\n // Auto-patch docker-compose.yml and recreate cloudflared container so the\n // new named tunnel takes effect without manual intervention.\n let composeUpdated = false;\n let containerRestarted = false;\n\n const composePath = join(projectPath, 'docker-compose.yml');\n const { existsSync: fsExists } = await import('node:fs');\n if (fsExists(composePath)) {\n try {\n const { patchCloudflaredToNamedTunnel } = await import('./compose-generator.js');\n composeUpdated = patchCloudflaredToNamedTunnel(composePath, result.tunnelToken);\n logger.info('tunnel', `[${tunnelName}] compose patch: composeUpdated=${composeUpdated}`);\n } catch (e) {\n logger.warn('tunnel', `[${tunnelName}] compose patch failed: ${e instanceof Error ? e.message : e}`);\n }\n\n if (composeUpdated) {\n try {\n const { execa: execaTunnel } = await import('execa');\n const up = await execaTunnel(\n 'docker',\n ['compose', '-f', composePath, 'up', '-d', '--force-recreate', 'cloudflared'],\n { cwd: projectPath, reject: false },\n );\n containerRestarted = up.exitCode === 0;\n if (!containerRestarted) {\n logger.warn('tunnel', `[${tunnelName}] cloudflared recreate failed (exit ${up.exitCode}): ${up.stderr}`);\n } else {\n logger.info('tunnel', `[${tunnelName}] cloudflared container recreated`);\n }\n } catch (e) {\n logger.warn('tunnel', `[${tunnelName}] cloudflared recreate exception: ${e instanceof Error ? e.message : e}`);\n }\n }\n\n // Configure Cloudflare ingress rules for all active built-in services\n if (cf.apiToken && cf.accountId && cf.tunnelId && zoneName) {\n try {\n const { configureTunnelIngress, getActiveServiceRoutes } = await import('./cloudflare-client.js');\n const routes = getActiveServiceRoutes(state).map((r) => ({ ...r, domain: zoneName }));\n await configureTunnelIngress(cf.apiToken, cf.accountId, cf.tunnelId, zoneName, routes);\n steps.push({ step: 'ingress_configured', success: true, services: routes.map((r) => r.subdomain) });\n logger.info('tunnel', `[${tunnelName}] ingress configured for: ${routes.map((r) => r.subdomain).join(', ')}`);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n steps.push({ step: 'ingress_configured', success: false, detail: msg });\n logger.warn('tunnel', `[${tunnelName}] ingress configure failed (non-fatal): ${msg}`);\n }\n\n // Create DNS CNAME records for each active built-in service (per-service, non-fatal)\n const { createDnsRecord, getActiveServiceRoutes: getRoutes } = await import('./cloudflare-client.js');\n const dnsRoutes = getRoutes(state);\n const dnsResults: string[] = [];\n const dnsFailed: string[] = [];\n for (const route of dnsRoutes) {\n try {\n await createDnsRecord(cf.apiToken, cf.zoneId, cf.tunnelId, route.subdomain, zoneName);\n dnsResults.push(route.subdomain);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n logger.warn('tunnel', `[${tunnelName}] DNS CNAME for ${route.subdomain} failed (non-fatal): ${msg}`);\n dnsFailed.push(route.subdomain);\n }\n }\n steps.push({ step: 'dns_created', success: dnsFailed.length === 0, services: dnsResults, ...(dnsFailed.length > 0 ? { detail: `skipped: ${dnsFailed.join(', ')}` } : {}) });\n }\n\n // Patch built-in service env vars for subdomain routing (Gitea, Nextcloud, pgAdmin, FileBrowser)\n if (zoneName) {\n try {\n const { patchBuiltinServicesForNamedTunnel } = await import('./compose-generator.js');\n const patchedServices = patchBuiltinServicesForNamedTunnel(composePath, zoneName);\n steps.push({ step: 'services_env_patched', success: true, services: patchedServices });\n logger.info('tunnel', `[${tunnelName}] env patched for: ${patchedServices.join(', ') || 'none'}`);\n\n // Restart services whose env vars changed\n if (patchedServices.length > 0) {\n try {\n const { execa: execaSvc } = await import('execa');\n const restart = await execaSvc(\n 'docker',\n ['compose', '-f', composePath, 'up', '-d', '--force-recreate', ...patchedServices],\n { cwd: projectPath, reject: false },\n );\n const restarted = restart.exitCode === 0;\n steps.push({ step: 'services_restarted', success: restarted, services: patchedServices });\n if (!restarted) {\n logger.warn('tunnel', `[${tunnelName}] service restart failed (exit ${restart.exitCode}): ${restart.stderr}`);\n } else {\n logger.info('tunnel', `[${tunnelName}] restarted: ${patchedServices.join(', ')}`);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n steps.push({ step: 'services_restarted', success: false, detail: msg });\n logger.warn('tunnel', `[${tunnelName}] service restart exception: ${msg}`);\n }\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n steps.push({ step: 'services_env_patched', success: false, detail: msg });\n logger.warn('tunnel', `[${tunnelName}] env patch failed (non-fatal): ${msg}`);\n }\n }\n } else {\n logger.warn('tunnel', `[${tunnelName}] compose file not found at ${composePath} — cloudflared must be updated manually`);\n }\n\n json(res, 200, {\n success: true,\n tunnelId: result.tunnelId,\n tunnelName: tunnelName.trim(),\n composeUpdated,\n containerRestarted,\n steps,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.toLowerCase().includes('already exists')) {\n json(res, 400, {\n success: false,\n error: 'TUNNEL_NAME_CONFLICT',\n message: `A tunnel named \"${tunnelName}\" already exists in your Cloudflare account. Choose a different name.`,\n });\n } else {\n json(res, 400, {\n success: false,\n error: 'TUNNEL_CREATE_FAILED',\n message: msg,\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Settings API handlers (T037-T038)\n// ---------------------------------------------------------------------------\n\nfunction handleSettingsCloudflareGet(\n res: ServerResponse,\n state: WizardState | null,\n): void {\n if (!state) {\n json(res, 200, { configured: false });\n return;\n }\n\n const cf = state.domain.cloudflare;\n const mask = (s: string | undefined) => !s ? 'not set' : s.length > 6 ? s.slice(0, 3) + '***' + s.slice(-3) : '***set***';\n\n json(res, 200, {\n configured: !!(cf.apiToken && cf.accountId && cf.zoneId),\n accountId: mask(cf.accountId),\n zoneId: mask(cf.zoneId),\n zoneName: cf.zoneName || '',\n tunnelId: mask(cf.tunnelId),\n tunnelName: cf.tunnelName || '',\n apiTokenSet: !!cf.apiToken,\n apiTokenValid: !!cf.apiToken, // Validated on save, assumed valid if set\n projectName: state.projectName || '',\n });\n}\n\nasync function handleSettingsCloudflarePut(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n let parsed: { apiToken?: string; accountId?: string; zoneId?: string; tunnelId?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { accountId, zoneId, tunnelId } = parsed;\n // Allow zone/tunnel saves without re-sending the token when it's already stored in memory\n const apiToken = parsed.apiToken || state.domain.cloudflare.apiToken;\n if (!apiToken) {\n json(res, 400, { success: false, error: 'MISSING_TOKEN', message: 'apiToken is required' });\n return;\n }\n\n // Verify the token\n try {\n const result = await verifyToken(apiToken);\n if (!result.valid) {\n json(res, 400, {\n success: false,\n error: 'INVALID_TOKEN',\n message: 'API token verification failed. Ensure the token has Tunnel:Edit, DNS:Edit, Zone:Read permissions.',\n });\n return;\n }\n\n // Update state in-place so in-memory wizardState is also updated immediately\n const { saveState: save } = await import('../wizard/state.js');\n state.domain.cloudflare.apiToken = apiToken;\n\n // Prefer explicit accountId param, then existing stored value, then getAccounts() (requires Account:Read)\n let resolvedAccountId = accountId || state.domain.cloudflare.accountId\n || await (await import('./cloudflare-client.js')).getAccounts(apiToken)\n .then((a) => a[0]?.id ?? '').catch(() => '');\n\n if (zoneId) state.domain.cloudflare.zoneId = zoneId;\n if (tunnelId) state.domain.cloudflare.tunnelId = tunnelId;\n\n // Get zone name for response and extract accountId from zone when getAccounts() returned nothing\n let zoneName = state.domain.cloudflare.zoneName;\n if (zoneId) {\n try {\n const { getZones } = await import('./cloudflare-client.js');\n const zones = await getZones(apiToken);\n const found = zones.find((z) => z.id === zoneId);\n if (found) {\n zoneName = found.name;\n state.domain.cloudflare.zoneName = zoneName;\n // accountId from zone (requires only Zone:Read — always works)\n if (!resolvedAccountId && found.accountId) {\n resolvedAccountId = found.accountId;\n }\n }\n } catch { /* non-critical */ }\n }\n\n if (resolvedAccountId) state.domain.cloudflare.accountId = resolvedAccountId;\n save(state);\n\n json(res, 200, {\n success: true,\n verified: true,\n email: result.email ?? '',\n zoneName,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,SAAS,oBAA6D;AACtE,SAAS,wBAAwB;AACjC,SAAS,MAAM,SAAS,eAAe;AACvC,SAAS,YAAY,cAAc,eAAe,UAAU,wBAAwB;AACpF,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,OAAO,eAAe;AA0EtB,IAAM,WAAW,KAAK,cAAc,YAAY,GAAG,GAAG,aAAa;AAMnE,IAAM,gBAAgB,KAAK,UAAU,wBAAwB;AAE7D,IAAM,aAAqC;AAAA,EACzC,SAAU;AAAA,EACV,OAAU;AAAA,EACV,QAAU;AAAA,EACV,QAAU;AAAA,EACV,SAAU;AAAA,EACV,QAAU;AAAA,EACV,QAAU;AAAA,EACV,QAAU;AAAA,EACV,SAAU;AAAA,EACV,QAAU;AAAA,EACV,SAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAU;AAAA,EACV,gBAAgB;AAClB;AAEA,SAAS,gBAAgB,UAAkB,KAAqB,aAAa,KAAW;AACtF,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,OAAO,WAAW,QAAQ,QAAQ,EAAE,YAAY,CAAC,KAAK;AAC5D,QAAM,WAAW,IAAI,OAAO,uCAAuC,EAAE,KAAK,QAAQ;AAClF,QAAM,eAAe,WAAW,wCAAwC;AACxE,MAAI,UAAU,YAAY;AAAA,IACxB,gBAAgB;AAAA,IAChB,kBAAkB,KAAK;AAAA,IACvB,iBAAiB;AAAA,EACnB,CAAC;AACD,mBAAiB,QAAQ,EAAE,KAAK,GAAG;AACrC;AAGA,IAAM,YAAY,MAAM;AACtB,QAAM,aAAa;AAAA,IACjB,KAAK,UAAU,wBAAwB;AAAA,IACvC,KAAK,UAAU,2BAA2B;AAAA,EAC5C;AACA,aAAW,KAAK,YAAY;AAC1B,QAAI,WAAW,CAAC,EAAG,QAAO,aAAa,GAAG,OAAO;AAAA,EACnD;AAEA,SAAO;AACT,GAAG;AAGH,IAAM,eAAe,MAAM;AACzB,QAAM,aAAa;AAAA,IACjB,KAAK,UAAU,2BAA2B;AAAA,IAC1C,KAAK,UAAU,8BAA8B;AAAA,EAC/C;AACA,aAAW,KAAK,YAAY;AAC1B,QAAI,WAAW,CAAC,EAAG,QAAO,aAAa,CAAC;AAAA,EAC1C;AACA,SAAO;AACT,GAAG;AAqBH,IAAM,qBAAwD;AAAA,EAC5D,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,cAAc;AAAA,EAChB;AAAA,EACA,qBAAqB;AAAA,IACnB,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,kBAAkB;AAAA,MAChB,EAAE,OAAO,QAAQ,OAAO,YAAY;AAAA,MACpC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,MAC/B,EAAE,OAAO,QAAQ,OAAO,UAAU;AAAA,MAClC,EAAE,OAAO,MAAM,OAAO,aAAa;AAAA,IACrC;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,kBAAkB;AAAA,MAChB,EAAE,OAAO,QAAQ,OAAO,YAAY;AAAA,MACpC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,MAC/B,EAAE,OAAO,QAAQ,OAAO,UAAU;AAAA,MAClC,EAAE,OAAO,MAAM,OAAO,aAAa;AAAA,IACrC;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,kBAAkB;AAAA,MAChB,EAAE,OAAO,QAAQ,OAAO,YAAY;AAAA,MACpC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,MAC/B,EAAE,OAAO,QAAQ,OAAO,mBAAmB;AAAA,MAC3C,EAAE,OAAO,YAAY,OAAO,aAAa;AAAA,IAC3C;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAMA,IAAM,eAAuC;AAAA,EAC3C,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,SAAS;AACX;AAMA,IAAM,SAAS,IAAI,UAAU;AAE7B,IAAM,oBAAoB,oBAAI,IAAI,CAAC,WAAW,SAAS,SAAS,OAAO,CAAC;AAExE,IAAM,oBAAoB,oBAAI,IAAI,CAAC,mBAAmB,mBAAmB,aAAa,CAAC;AAIvF,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EAAc;AAAA,EAAS;AAAA,EACvB;AACF,CAAC;AAID,IAAM,wBAAgD;AAAA,EACpD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,WAAW;AAAA,EACX,SAAS;AACX;AAGA,IAAM,kBAAkB,oBAAI,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC;AAEhD,SAAS,eAAe,WAAmD;AACzE,QAAM,OAAO,UAAU,SAAS,CAAC,GAC9B,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,cAAc,CAAC,gBAAgB,IAAI,EAAE,UAAU,CAAC,EACpF,KAAK,CAAC,GAAG,MAAM,EAAE,aAAc,EAAE,UAAW;AAC/C,SAAO,IAAI,CAAC,GAAG,cAAc;AAC/B;AAMA,SAAS,KAAK,KAAqB,QAAgB,MAAqB;AACtE,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,MAAI,UAAU,QAAQ,EAAE,gBAAgB,oBAAoB,kBAAkB,OAAO,WAAW,OAAO,EAAE,CAAC;AAC1G,MAAI,IAAI,OAAO;AACjB;AAEA,eAAe,SAAS,KAAuC;AAC7D,SAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,QAAI,OAAO;AACX,QAAI,GAAG,QAAQ,CAAC,MAAO,QAAQ,CAAE;AACjC,QAAI,GAAG,OAAO,MAAMA,SAAQ,IAAI,CAAC;AAAA,EACnC,CAAC;AACH;AAEA,eAAe,kBACb,MACA,KACA,QACA,OACA,cACA,SAAiC,uBACjC,iBAAiB,IACjB,aACA,aACe;AAEf,QAAM,sBAA8C;AAAA,IAClD,OAAO;AAAA,IACP,WAAW;AAAA,IACX,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,gBAAgB,MAAM,OAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC/D,UAAM,WAA4B,CAAC;AAEnC,eAAW,KAAK,eAAe;AAC7B,YAAM,iBAAiB,EAAE,SAAS,4BAA4B;AAC9D,UAAI,CAAC,eAAgB;AACrB,UAAI,kBAAkB,IAAI,cAAc,EAAG;AAE3C,YAAM,aAAa,EAAE,SAAS,wCAAwC,KAAK;AAK3E,UAAI,eAAe,YAAY,OAAO,GAAG;AACvC,YAAI,cAAc,WAAW,WAAW,YAAY,KAAK,CAAC,YAAY,IAAI,UAAU,GAAG;AACrF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,MAAM,qBAAqB,cAAc;AAC/C,YAAM,IAAI,EAAE;AACZ,YAAM,SAAS,MAAM,YAAY,YAAY,MAAM,WAAW,YAAa;AAC3E,YAAM,OAAO,eAAe,CAAC,KAAK,KAAK,QAAQ,CAAC,KAAK;AAOrD,YAAM,SAAS,EAAE,UAAU,CAAC;AAC5B,YAAM,mBAAmB,oCAAoC,cAAc;AAC3E,YAAM,aACJ,OAAO,gBAAgB,KAAK,OAAO,OAAO,gBAAgB,CAAC,EAAE,SAAS,YAAY,IAC9E,CAAC,kBAAkB,OAAO,gBAAgB,CAAC,IAC3C,OAAO,QAAQ,MAAM,EAAE;AAAA,QACrB,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,uBAAuB,KAAK,EAAE,SAAS,OAAO,KAAK,OAAO,CAAC,EAAE,SAAS,YAAY;AAAA,MAC3G;AACN,UAAI,cAAc;AAClB,UAAI,YAAY;AACd,cAAM,YAAY,OAAO,WAAW,CAAC,CAAC,EAAE,MAAM,yBAAyB;AACvE,YAAI,UAAW,eAAc,UAAU,CAAC;AAAA,MAC1C;AAGA,YAAM,iBAAiB,OAAO,KAAK,MAAM,EAAE;AAAA,QACzC,CAAC,MAAM,EAAE,SAAS,eAAe;AAAA,MACnC;AACA,YAAM,gBAAiB,eAAe,CAAC,iBAAkB,cAAc;AAGvE,UAAI,cAA6B;AACjC,YAAM,QAAQ;AACd,YAAM,aAAa,aAAa,QAAQ,YAAY,cAAc;AAClE,YAAM,cAAc,aAAa,QAAQ,YAAY,YAAY,aAAa,QAAQ,QAAQ;AAE9F,UAAI,eAAe,WAAW,aAAa;AAEzC,cAAM,MAAM,oBAAoB,cAAc;AAC9C,YAAI,KAAK;AACP,wBAAc,WAAW,GAAG,IAAI,WAAW;AAAA,QAC7C;AAGA,YAAI,CAAC,aAAa;AAChB,gBAAM,kBAAkB,CAAC,gBAAgB,eAAe,QAAQ,aAAa,EAAE,CAAC;AAChF,gBAAM,QAAQ,aAAa,qBAAqB,CAAC,GAAG;AAAA,YAClD,CAACC,OAAM,gBAAgB,SAASA,GAAE,OAAO;AAAA,UAC3C;AACA,cAAI,KAAM,eAAc,WAAW,KAAK,QAAQ;AAAA,QAClD;AAAA,MACF,WAAW,SAAS,aAAa;AAE/B,YAAI,UAAU;AAGd,cAAM,aAAa,OAAO,mBAAmB,KAAK;AAClD,YAAI,eAAe,mBAAoB,mBAAmB,aAAa,QAAQ,SAAS,YAAY,GAAI;AACtG,qBAAW;AAAA,QACb;AACA,sBAAc,MAAM,QAAQ,OAAO,EAAE,IAAI;AAAA,MAC3C;AACA,UAAI,CAAC,eAAe,SAAS,eAAe,SAAS;AAEnD,cAAM,eAAuC;AAAA,UAC3C,SAAS;AAAA,UAAI,OAAO;AAAA,UAAQ,WAAW;AAAA,UAAU,SAAS;AAAA,UAC1D,UAAU;AAAA,UAAa,aAAa;AAAA,UAAU,OAAO;AAAA,QACvD;AACA,YAAI,aAAa,cAAc,MAAM,QAAW;AAC9C,wBAAc,MAAM,QAAQ,OAAO,EAAE,IAAI,aAAa,cAAc;AAAA,QACtE;AAAA,MACF;AAGA,YAAM,UAAW,cAAc,WAAW,WAAW,YAAY,IAC7D,WAAW,MAAM,aAAa,MAAM,EAAE,QAAQ,UAAU,EAAE,KAAK,SAC/D;AAIJ,YAAM,+BAA+B,oBAAI,IAAI,CAAC,YAAY,SAAS,CAAC;AACpE,YAAM,iBAAiB,EAAE,SAAS,4BAA4B,KAAK;AACnE,YAAM,YAAY,6BAA6B,IAAI,cAAc,KAAK,CAAC,CAAC;AACxE,YAAM,YAAc,YAAY,GAAG,cAAc,IAAI,cAAc,KAAK;AACxE,YAAM,cAAc,YAChB,GAAG,cAAc,IAAI,mBAAmB,aAAa,UAAU,MAAM,KACpE,KAAK,QAAQ;AAElB,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM,MAAM,UAAU,cAAc,IAAI;AAAA,QACxC;AAAA,QACA,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ,EAAE,QAAQ,WAAW,IAAI,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI;AAAA,QACpE,MAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,QAKd,KAAK,QAAQ,CAAC,iBAAiB,IAAI,cAAc,IAC7C,OAAO,cAAc,KAAK,oBAAoB,IAAI,GAAG,aAAa,KAClE;AAAA,QACJ;AAAA,QACA,WAAW,CAAC,kBAAkB,IAAI,cAAc;AAAA,QAChD;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE;AAC/D,SAAK,KAAK,KAAK;AAAA,MACb;AAAA,MACA,SAAS,EAAE,OAAO,SAAS,QAAQ,SAAS,SAAS,SAAS,SAAS,QAAQ;AAAA,IACjF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAC;AAAA,EACtE;AACF;AAEA,SAAS,UAAU,IAAoB;AACrC,MAAI,CAAC,WAAW,SAAS,OAAO,EAAE,SAAS,EAAE,EAAG,QAAO;AACvD,MAAI,CAAC,cAAc,OAAO,EAAE,SAAS,EAAE,EAAG,QAAO;AACjD,MAAI,CAAC,aAAa,SAAS,aAAa,EAAE,SAAS,EAAE,EAAG,QAAO;AAC/D,MAAI,CAAC,UAAU,EAAE,SAAS,EAAE,EAAG,QAAO;AACtC,MAAI,CAAC,OAAO,EAAE,SAAS,EAAE,EAAG,QAAO;AACnC,MAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,EAAG,QAAO;AAC5C,SAAO;AACT;AAEA,eAAe,oBACb,MACA,KACA,OACA,OACA,cACe;AACf,QAAM,YAAY,MAAM,CAAC;AACzB,QAAM,SAAS,MAAM,CAAC;AAEtB,MAAI,CAAC,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,MAAM,GAAG;AACrD,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,CAAC;AAC3D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,aAAa,MAAM,OAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC5D,UAAM,QAAQ,WAAW;AAAA,MACvB,CAAC,MAAM,EAAE,SAAS,4BAA4B,MAAM;AAAA,IACtD;AAEA,QAAI,CAAC,OAAO;AACV,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,MAAM,QAAQ,CAAC;AAC5E;AAAA,IACF;AAEA,QAAI,WAAW,WAAW,MAAM,UAAU,WAAW;AACnD,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,8BAA8B,MAAM,kBAAkB,CAAC;AAC/F;AAAA,IACF;AACA,QAAI,WAAW,UAAU,MAAM,UAAU,WAAW;AAClD,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,0BAA0B,MAAM,cAAc,CAAC;AACvF;AAAA,IACF;AAEA,UAAM,YAAY,OAAO,aAAa,MAAM,EAAE;AAC9C,QAAI,WAAW,SAAS;AACtB,YAAM,UAAU,MAAM;AAAA,IACxB,OAAO;AACL,YAAM,UAAU,KAAK;AAAA,IACvB;AAEA,UAAM,YAAY,WAAW,UAAU,YAAY;AACnD,SAAK,KAAK,KAAK,EAAE,SAAS,MAAM,IAAI,WAAW,QAAQ,UAAU,CAAC;AAAA,EACpE,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,GAAG,MAAM,QAAQ,CAAC;AAAA,EACtE;AACF;AAEA,eAAe,qBACb,MACA,KACA,QACA,MACA,aACe;AACf,MAAI;AACF,UAAM,EAAE,GAAG,IAAI,KAAK,MAAM,IAAI;AAC9B,QAAI,CAAC,IAAI;AAAE,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,CAAC;AAAG;AAAA,IAAQ;AAEpF,UAAM,SAAS,MAAM,WAAW,IAAI,WAAW;AAC/C,QAAI,OAAO,SAAS;AAClB,WAAK,KAAK,KAAK,EAAE,SAAS,MAAM,IAAI,QAAQ,aAAa,SAAS,WAAW,EAAE,SAAS,CAAC;AAAA,IAC3F,OAAO;AACL,YAAM,OAAO,OAAO,OAAO,SAAS,SAAS,IAAI,mBAAmB;AACpE,WAAK,KAAK,OAAO,OAAO,SAAS,SAAS,IAAI,MAAM,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC;AAAA,IACxG;AAAA,EACF,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,oBACb,KACA,KACA,OACA,OACA,aACe;AACf,QAAM,YAAY,MAAM,CAAC;AACzB,MAAI,CAAC,WAAW;AAAE,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,CAAC;AAAG;AAAA,EAAQ;AAE3F,MAAI,kBAAkB,IAAI,SAAS,GAAG;AACpC,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mCAAmC,SAAS,IAAI,MAAM,mBAAmB,CAAC;AAClH;AAAA,EACF;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACtD,QAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,MAAM;AAEhD,MAAI;AACF,UAAM,SAAS,MAAM,cAAc,WAAW,aAAa,EAAE,MAAM,CAAC;AACpE,QAAI,OAAO,SAAS;AAClB,WAAK,KAAK,KAAK,EAAE,SAAS,MAAM,IAAI,WAAW,eAAe,CAAC,MAAM,CAAC;AAAA,IACxE,OAAO;AACL,WAAK,KAAK,OAAO,OAAO,SAAS,WAAW,IAAI,MAAM,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,IACnH;AAAA,EACF,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,iBACb,MACA,KACA,QACA,OACA,cACe;AACf,MAAI;AACF,UAAM,YAAY,oBAAI,IAAY;AAClC,UAAM,aAAa,MAAM,OAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC5D,eAAW,KAAK,YAAY;AAC1B,YAAM,KAAK,EAAE,SAAS,4BAA4B;AAClD,UAAI,GAAI,WAAU,IAAI,EAAE;AAAA,IAC1B;AAEA,UAAM,UAAU,CAAC,GAAG,iBAAiB,OAAO,CAAC,EAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,IAAI,IAAI,EAAE,CAAC,EAC9C,IAAI,CAAC,SAAS;AAAA,MACb,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,aAAa;AAAA,MACb,UAAU,UAAU,IAAI,EAAE;AAAA,MAC1B,OAAO,IAAI;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,WAAW,UAAU,IAAI,IAAI,EAAE;AAAA,IACjC,EAAE;AAEJ,SAAK,KAAK,KAAK,EAAE,QAAQ,CAAC;AAAA,EAC5B,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,aACb,KACA,KACA,QACA,OACA,aACe;AACf,QAAM,aAAa,KAAK,QAAQ,GAAG,YAAY,SAAS;AAExD,MAAI,IAAI,WAAW,OAAO;AACxB,QAAI;AACF,YAAM,UAAU,YAAY,UAAU;AACtC,WAAK,KAAK,KAAK,EAAE,QAAQ,CAAC;AAAA,IAC5B,SAAS,KAAK;AACZ,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IACvD;AACA;AAAA,EACF;AAGA,MAAI;AACF,UAAM,SAAS,aAAa,aAAa,UAAU;AACnD,SAAK,KAAK,KAAK,EAAE,SAAS,MAAM,UAAU,OAAO,IAAI,QAAQ,YAAY,CAAC;AAAA,EAC5E,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAOA,IAAM,eAAe,oBAAI,IAAiD;AAG1E,IAAM,oBAAoB,oBAAI,IAAyC;AAEvE,SAAS,eAAe,SAAiB,MAAoB;AAC3D,QAAM,SAAS,oBAAoB,IAAI;AACvC,SAAO,KAAK,UAAU,IAAI,OAAO,KAAK,IAAI,EAAE;AAC5C,MAAI,CAAC,aAAa,IAAI,OAAO,EAAG,cAAa,IAAI,SAAS,CAAC,CAAC;AAC5D,QAAM,MAAM,aAAa,IAAI,OAAO;AACpC,MAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,KAAK,IAAI,EAAE,CAAC;AACzC,MAAI,IAAI,SAAS,IAAK,KAAI,MAAM;AAChC,oBAAkB,IAAI,OAAO,GAAG,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC;AAC5D;AAMO,SAAS,kBAAkB,UAA8B,CAAC,GAI/D;AACA,QAAM,OAAO,QAAQ,QAAQ;AAK7B,MAAI,cAAc,QAAQ,eAAe,QAAQ,IAAI;AACrD,MAAI,cAAkC;AACtC,QAAM,OAAO,eAAe;AAC5B,MAAI,MAAM;AACR,UAAM,QAAQ,UAAU,IAAI;AAC5B,QAAI,OAAO;AACT,oBAAc;AAEd,UAAI,CAAC,QAAQ,eAAe,MAAM,YAAa,eAAc,MAAM;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,YAAY,WAAW,IAAI,KAAK,gBAAgB,KAAK;AACvD,kBAAc,KAAK,QAAQ,GAAG,YAAY,MAAM,CAAC,CAAC;AAAA,EACpD;AAGA,QAAM,WAAW,aAAa,OAAO,YAAY;AACjD,QAAM,WAAW,aAAa,OAAO,YAAY;AAGjD,QAAM,WAAW,CAAC,MAAe,EAAE,SAAS,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,OAAO;AACxE,QAAM,WAAW,CAAC,MAAe,EAAE,SAAS,IAAI,EAAE,CAAC,IAAI,IAAI,OAAO,EAAE,SAAS,CAAC,IAAI;AAKlF,QAAM,qBAAqB,oBAAI,IAAY;AAC3C,qBAAmB,IAAI,WAAW;AAClC,MAAI;AAEF,UAAM,cAAc,KAAK,aAAa,2BAA2B;AACjE,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,OAAO,KAAK,MAAM,aAAa,aAAa,OAAO,CAAC;AAC1D,YAAM,UAA6B,MAAM,QAAQ,IAAI,IAAI,OAAQ,KAAK,UAAU,CAAC,IAAI,IAAI,CAAC;AAC1F,iBAAW,KAAK,SAAS;AACvB,YAAI,EAAE,OAAQ,oBAAmB,IAAI,EAAE,MAAM;AAAA,MAC/C;AAAA,IACF;AAEA,UAAM,eAAe,KAAK,QAAQ,GAAG,YAAY,WAAW;AAC5D,QAAI,WAAW,YAAY,GAAG;AAC5B,YAAM,OAAO,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC3D,iBAAW,OAAO,MAAM;AACtB,YAAI,IAAI,OAAQ,oBAAmB,IAAI,IAAI,MAAM;AAAA,MACnD;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAAkB;AAE1B,QAAM,aAA8B;AAAA,IAClC,eAAe,WAAW,SAAS,QAAQ,IAAI;AAAA,IAC/C,cAAc,WAAW,SAAS,QAAQ,IAAI;AAAA,IAC9C,gBAAgB,aAAa,QAAQ,YAAY;AAAA,IACjD,gBAAgB,aAAa,QAAQ,YAAY,kBAAkB;AAAA,IACnE,UAAU,aAAa,QAAQ,YAAY,YAAY;AAAA,IACvD,UAAU,aAAa,QAAQ,YAAY,YAAY;AAAA,EACzD;AAQA,QAAM,gBAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,sBAAsB,CAAC,CAAC,WAAW;AAMvC,iBAAe,uBAAsC;AACnD,QAAI,oBAAqB;AACzB,0BAAsB;AACtB,QAAI;AACF,YAAM,aAAa,MAAM,OAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC5D,YAAM,KAAK,WAAW;AAAA,QACpB,CAAC,MAAM,EAAE,SAAS,4BAA4B,MAAM;AAAA,MACtD;AACA,UAAI,CAAC,MAAM,GAAG,UAAU,UAAW;AACnC,YAAM,YAAY,OAAO,aAAa,GAAG,EAAE;AAC3C,YAAM,SAAS,MAAM,UAAU,KAAK,EAAE,QAAQ,MAAM,QAAQ,MAAM,MAAM,GAAG,CAAC;AAC5E,YAAM,SAAS,OAAO,SAAS,OAAO;AACtC,YAAM,QAAQ,OAAO,MAAM,oDAAoD;AAC/E,UAAI,OAAO;AACT,mBAAW,iBAAiB,WAAW,MAAM,CAAC,CAAC;AAC/C,mBAAW,iBAAiB;AAAA,MAC9B;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAMA,MAAI,sBAAsB,CAAC,EAAE,YAAY;AACzC,iBAAe,oBAAmC;AAChD,QAAI,oBAAqB;AACzB,0BAAsB;AACtB,QAAI;AACF,YAAM,aAAa,MAAM,OAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC5D,YAAM,KAAK,WAAW;AAAA,QACpB,CAAC,MAAM,EAAE,SAAS,4BAA4B,MAAM;AAAA,MACtD;AACA,UAAI,CAAC,GAAI;AACT,YAAM,OAAO,MAAM,OAAO,aAAa,GAAG,EAAE,EAAE,QAAQ;AACtD,YAAM,SAAmB,KAAK,QAAQ,OAAO,CAAC;AAC9C,UAAI,IAAI;AACR,UAAI,IAAI;AACR,iBAAW,SAAS,QAAQ;AAC1B,YAAI,CAAC,KAAK,MAAM,WAAW,uBAAuB,GAAG;AACnD,cAAI,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,QACxC;AACA,YAAI,CAAC,KAAK,MAAM,WAAW,2BAA2B,GAAG;AACvD,cAAI,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAAA,QACxC;AAAA,MACF;AACA,UAAI,KAAK,GAAG;AACV,mBAAW,gBAAgB,SAAS,KAAK,OAAO;AAChD,mBAAW,eAAe,SAAS,CAAC;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,OAAO,KAAsB,QAAwB;AAC/E,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AACzD,UAAM,OAAO,MAAM,SAAS,GAAG;AAG/B,QAAI,UAAU,+BAA+B,GAAG;AAChD,QAAI,UAAU,gCAAgC,4BAA4B;AAC1E,QAAI,UAAU,gCAAgC,gCAAgC;AAE9E,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,QAAQ,aAAa;AAC/C,UAAI,UAAU,KAAK,EAAE,gBAAgB,iBAAiB,iBAAiB,wBAAwB,CAAC;AAChG,UAAI,IAAI,QAAQ;AAChB;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,QAAQ,gBAAgB;AAClD,UAAI,aAAa;AACf,YAAI,UAAU,KAAK,EAAE,gBAAgB,gBAAgB,iBAAiB,wBAAwB,CAAC;AAC/F,YAAI,IAAI,WAAW;AAAA,MACrB,OAAO;AACL,YAAI,UAAU,KAAK,EAAE,gBAAgB,iBAAiB,iBAAiB,wBAAwB,CAAC;AAChG,YAAI,IAAI,QAAQ;AAAA,MAClB;AACA;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,IAAI,WAAW,UAAU,GAAG;AACtD,YAAM,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;AACjC,YAAM,WAAW,QAAQ,eAAe,MAAM,QAAQ;AACtD,UAAI,CAAC,SAAS,WAAW,aAAa,GAAG;AACvC,YAAI,UAAU,GAAG;AAAG,YAAI,IAAI,WAAW;AAAG;AAAA,MAC5C;AACA,UAAI,WAAW,QAAQ,KAAK,SAAS,QAAQ,EAAE,OAAO,GAAG;AACvD,wBAAgB,UAAU,GAAG;AAC7B;AAAA,MACF;AACA,UAAI,UAAU,GAAG;AAAG,UAAI,IAAI,WAAW;AACvC;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,CAAC,IAAI,WAAW,OAAO,GAAG;AACpD,YAAM,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC;AAEjC,YAAM,YAAY,QAAQ,eAAe,OAAO,aAAa,MAAM,gBAAgB,SAAS;AAC5F,UAAI,UAAU,WAAW,aAAa,KAAK,WAAW,SAAS,KAAK,SAAS,SAAS,EAAE,OAAO,GAAG;AAChG,wBAAgB,WAAW,GAAG;AAC9B;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,eAAe,YAAY;AAClD,UAAI,WAAW,SAAS,GAAG;AACzB,wBAAgB,WAAW,GAAG;AAC9B;AAAA,MACF;AAEA,UAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,UAAI,IAAI,gEAAgE;AACxE;AAAA,IACF;AAIA,QAAI,MAAM,CAAC,MAAM,OAAO;AACtB,UAAI;AACF,YAAI,MAAM,CAAC,MAAM,YAAY,IAAI,WAAW,OAAO;AAGjD,cAAI,aAAa,OAAO,UAAU;AAChC,kBAAM,WAAW,IAAI,QAAQ,kBAAkB;AAC/C,gBAAI,CAAC,YAAY,aAAa,YAAY,MAAM,UAAU;AACxD,mBAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,SAAS,0BAA0B,CAAC;AAC5E;AAAA,YACF;AAAA,UACF;AACA,eAAK,KAAK,KAAK,EAAE,QAAQ,MAAM,SAAS,QAAQ,CAAC;AACjD;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,MAAM,aAAa,IAAI,WAAW,OAAO;AAC7E,gBAAM,YAAY,aAAa,OAAO,YAAY;AAClD,gBAAM,UAAU,EAAE,GAAG,mBAAmB;AACxC,cAAI,QAAQ,YAAY,GAAG;AACzB,oBAAQ,YAAY,IAAI;AAAA,cACtB,GAAG,QAAQ,YAAY;AAAA,cACvB,aAAa;AAAA,gBACX,GAAG,QAAQ,YAAY,EAAE;AAAA,gBACzB,SAAS,eAAe,SAAS;AAAA,cACnC;AAAA,cACA,kBAAkB,QAAQ,YAAY,EAAE,kBAAkB;AAAA,gBAAI,CAAC,MAC7D,EAAE,UAAU,SAAS,EAAE,GAAG,GAAG,OAAO,UAAU,IAAI;AAAA,cACpD;AAAA,YACF;AAAA,UACF;AACA,eAAK,KAAK,KAAK,EAAE,SAAS,SAAS,aAAa,CAAC;AACjD;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,YAAY,IAAI,WAAW,OAAO;AACjD,gBAAM,qBAAqB;AAC3B,gBAAM,kBAAkB;AACxB,eAAK,KAAK,KAAK;AAAA,YACb,eAAe,WAAW;AAAA,YAC1B,cAAc,WAAW;AAAA,YACzB,gBAAgB,WAAW;AAAA,YAC3B,gBAAgB,WAAW;AAAA,YAC3B,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,UACvB,CAAC;AACD;AAAA,QACF;AAEA,YAAI,MAAM,CAAC,MAAM,YAAY;AAC3B,cAAI,IAAI,WAAW,SAAS,MAAM,WAAW,GAAG;AAC9C,kBAAM,kBAAkB,KAAK,KAAK,OAAO,MAAM,aAAa,eAAe,WAAW,gBAAgB,oBAAoB,WAAW;AACrI;AAAA,UACF;AACA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,WAAW;AACnD,kBAAM,qBAAqB,KAAK,KAAK,OAAO,MAAM,WAAW;AAC7D;AAAA,UACF;AAEA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,KAAK,CAAC,SAAS,MAAM,EAAE,SAAS,MAAM,CAAC,KAAK,EAAE,GAAG;AACnF,kBAAM,oBAAoB,KAAK,KAAK,OAAO,MAAM,WAAW;AAC5D;AAAA,UACF;AAEA,cAAI,IAAI,WAAW,YAAY,MAAM,CAAC,GAAG;AACvC,kBAAM,oBAAoB,KAAK,KAAK,OAAO,MAAM,WAAW;AAC5D;AAAA,UACF;AAAA,QACF;AAEA,YAAI,MAAM,CAAC,MAAM,aAAa,IAAI,WAAW,OAAO;AAClD,gBAAM,iBAAiB,KAAK,KAAK,OAAO,MAAM,WAAW;AACzD;AAAA,QACF;AAEA,YAAI,MAAM,CAAC,MAAM,UAAU;AACzB,gBAAM,aAAa,KAAK,KAAK,OAAO,MAAM,WAAW;AACrD;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,QAAQ;AACvB,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,SAAS;AAChD,kBAAM,QAAQ,MAAM,YAAY,WAAW;AAC3C,iBAAK,KAAK,KAAK,KAAK;AACpB;AAAA,UACF;AACA,cAAI,IAAI,WAAW,OAAO;AACxB,kBAAM,OAAO,IAAI,IAAI,KAAK,kBAAkB;AAC5C,kBAAM,UAAU,KAAK,aAAa,IAAI,QAAQ;AAC9C,kBAAM,SAAS,KAAK,aAAa,IAAI,OAAO;AAC5C,kBAAM,WAAW,KAAK,aAAa,IAAI,SAAS;AAChD,kBAAM,QAAQ,KAAK,aAAa,IAAI,OAAO,KAAK;AAChD,kBAAM,QAAQ,KAAK,aAAa,IAAI,OAAO,KAAK;AAChD,kBAAM,SAAS,KAAK,aAAa,IAAI,QAAQ,KAAK;AAClD,kBAAM,QAAQ,SAAS,KAAK,aAAa,IAAI,OAAO,KAAK,OAAO,EAAE;AAClE,kBAAM,SAAS,SAAS,KAAK,aAAa,IAAI,QAAQ,KAAK,KAAK,EAAE;AAElE,kBAAM,SAAS,MAAM;AAAA,cACnB;AAAA,gBACE,SAAS,UAAU,CAAC,OAAoB,IAAI;AAAA,gBAC5C,QAAQ,SAAS,CAAC,MAAyB,IAAI;AAAA,gBAC/C,UAAU,WAAW,CAAC,QAAQ,IAAI;AAAA,gBAClC;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,OAAO,MAAM,KAAK,IAAI,MAAM;AAAA,gBAC5B,QAAQ,MAAM,MAAM,IAAI,IAAI;AAAA,cAC9B;AAAA,cACA;AAAA,YACF;AACA,iBAAK,KAAK,KAAK,MAAM;AACrB;AAAA,UACF;AAAA,QACF;AAEA,YAAI,MAAM,CAAC,MAAM,QAAQ;AACvB,cAAI,IAAI,WAAW,SAAS,MAAM,WAAW,GAAG;AAC9C,kBAAM,OAAO,MAAM,SAAS;AAE5B,kBAAM,UAAU,iBAAiB;AACjC,kBAAM,eAAe,oBAAI,IAA+B;AACxD,uBAAW,KAAK,SAAS;AAAE,kBAAI,EAAE,WAAW,UAAW,cAAa,IAAI,EAAE,SAAS,CAAC;AAAA,YAAG;AAEvF,kBAAM,aAAa,KAAK,aAAa,2BAA2B;AAChE,kBAAM,YAAY,oBAAI,IAA6B;AACnD,gBAAI,WAAW,UAAU,GAAG;AAC1B,kBAAI;AACF,sBAAM,MAAM,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AACxD,sBAAM,QAA2B,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAChE,2BAAW,KAAK,MAAO,WAAU,IAAI,EAAE,SAAS,CAAC;AAAA,cACnD,QAAQ;AAAA,cAA4B;AAAA,YACtC;AACA,kBAAM,eAAe,KAAK,IAAI,CAAC,MAAM;AACnC,oBAAM,aAAa,aAAa,IAAI,EAAE,IAAI,KAAK;AAC/C,oBAAM,KAAK,WAAW;AAEtB,oBAAM,SAAS,EAAE,SAAS,iBAAiB,EAAE,UAAU,UAAU,IAAI,EAAE,OAAO,IAAI;AAClF,oBAAM,eAAe,SACjB,OAAO,cAAc,QACrB,CAAC,EAAE,EAAE,WAAW,aAAa,EAAE,OAAO,GAAG,cAAc;AAC3D,kBAAI;AACJ,kBAAI;AACJ,kBAAI,kBAAiC;AACrC,kBAAI,qBAAoC;AAExC,oBAAM,cAAc,aAAa,qBAAqB,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI;AAC1F,kBAAI,cAAc;AAEhB,oBAAI,eAAe;AACnB,sBAAM,YAAY,KAAK,EAAE,QAAQ,MAAM;AACvC,oBAAI,WAAW,SAAS,GAAG;AACzB,wBAAM,eAAe,aAAa,WAAW,OAAO;AACpD,wBAAM,cAAc,aAAa,MAAM,uBAAuB;AAC9D,sBAAI,YAAa,gBAAe,SAAS,YAAY,CAAC,GAAG,EAAE;AAAA,gBAC7D;AACA,2BAAW,oBAAoB,YAAY;AAC3C,8BAAc,aAAa,WAAW,WAAW,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,QAAQ;AACnH,kCAAkB,EAAE,OAAO,oBAAoB,EAAE,IAAI,KAAK;AAC1D,qCAAqB,aAAa,WAAW,WAAW,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,KAAK;AAAA,cACzH,OAAO;AAEL,2BAAW,EAAE,OAAO,oBAAoB,EAAE,IAAI,KAAK;AACnD,oBAAI,EAAE,QAAQ;AACZ,wBAAM,KAAK,eAAe,EAAE,MAAM;AAClC,sBAAI,MAAM,SAAU,aAAY;AAAA,gBAClC;AACA,8BAAc,aAAa,WAAW,WAAW,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,KAAK;AAAA,cAClH;AACA,qBAAO,EAAE,GAAG,GAAG,gBAAgB,YAAY,cAAc,MAAM,UAAU,aAAa,iBAAiB,mBAAmB;AAAA,YAC5H,CAAC;AACD,mBAAO,KAAK,gBAAgB,6BAA6B,KAAK,MAAM,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE;AACzH,iBAAK,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACrC;AAAA,UACF;AACA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,gBAAgB;AACvD,kBAAM,SAAS,KAAK,aAAa,2BAA2B;AAC5D,kBAAM,QAA2B,CAAC;AAElC,gBAAI,WAAW,MAAM,GAAG;AACtB,oBAAM,MAAM,KAAK,MAAM,aAAa,QAAQ,OAAO,CAAC;AACpD,oBAAM,cAAc,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AACnD,oBAAM,KAAK,GAAG,WAAW;AAAA,YAC3B;AAEA,kBAAM,UAAU,MAAM,SAAS;AAC/B,uBAAW,OAAO,SAAS;AACzB,kBAAI,IAAI,SAAS,iBAAiB,CAAC,IAAI,QAAS;AAEhD,kBAAI,MAAM,KAAK,CAAC,MAAM,EAAE,UAAU,IAAI,UAAU,EAAE,WAAW,IAAI,MAAM,EAAG;AAC1E,oBAAM,UAAU,KAAK,IAAI,QAAQ,MAAM;AACvC,kBAAI;AACJ,kBAAI;AACJ,kBAAI;AACJ,kBAAI;AACJ,kBAAI,WAAW,OAAO,GAAG;AACvB,sBAAM,aAAa,aAAa,SAAS,OAAO;AAChD,sBAAM,UAAU,WAAW,MAAM,uBAAuB;AACxD,oBAAI,QAAS,gBAAe,SAAS,QAAQ,CAAC,GAAI,EAAE;AACpD,sBAAM,UAAU,WAAW,MAAM,kBAAkB;AACnD,oBAAI,QAAS,YAAW,QAAQ,CAAC,EAAG,KAAK;AACzC,sBAAM,UAAU,WAAW,MAAM,gBAAgB;AACjD,oBAAI,QAAS,UAAS,QAAQ,CAAC,EAAG,KAAK;AACvC,sBAAM,UAAU,WAAW,MAAM,gBAAgB;AACjD,oBAAI,QAAS,UAAS,QAAQ,CAAC,EAAG,KAAK;AAAA,cACzC;AACA,oBAAM,aAAa,aAAa,IAAI,OAAO;AAC3C,oBAAM,KAAK;AAAA,gBACT,SAAS,IAAI;AAAA,gBACb,QAAQ,IAAI;AAAA,gBACZ,YAAY,oBAAoB,IAAI,IAAI;AAAA,gBACxC,aAAa,eAAe,oBAAoB,YAAY,KAAK;AAAA,gBACjE,WAAW,YAAY,aAAa;AAAA,gBACpC,MAAM,IAAI;AAAA,gBACV;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,QAAQ,IAAI;AAAA,cACd,CAAC;AAAA,YACH;AACA,mBAAO,KAAK,gBAAgB,0CAA0C,MAAM,MAAM,2BAA2B,MAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,OAAO,EAAE,MAAM,gBAAgB,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG;AACrR,iBAAK,KAAK,KAAK,EAAE,cAAc,MAAM,CAAC;AACtC;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,kBAAkB,MAAM,CAAC,MAAM,MAAM,CAAC,MAAM,UAAU,MAAM,CAAC,MAAM,UAAU;AACrH,kBAAM,UAAU,mBAAmB,MAAM,CAAC,CAAC;AAC3C,kBAAM,SAAS,MAAM,CAAC;AACtB,kBAAM,SAAS,KAAK,aAAa,2BAA2B;AAC5D,gBAAI,CAAC,WAAW,MAAM,GAAG;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAG;AAAA,YAAQ;AACvF,kBAAM,QAAQ,KAAK,MAAM,aAAa,QAAQ,OAAO,CAAC;AACtD,kBAAM,UAA6B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACxE,kBAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO;AACtD,gBAAI,CAAC,MAAM;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,OAAO,cAAc,CAAC;AAAG;AAAA,YAAQ;AACtF,kBAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,OAAO,OAAO;AAC/C,gBAAI,WAAW,QAAQ;AACrB,oBAAM,QAAQ,UAAU,CAAC,WAAW,MAAM,GAAG,EAAE,KAAK,KAAK,OAAO,CAAC;AACjE,mBAAK,SAAS;AAAA,YAChB,OAAO;AACL,oBAAM,QAAQ,UAAU,CAAC,WAAW,MAAM,IAAI,GAAG,EAAE,KAAK,KAAK,OAAO,CAAC;AACrE,mBAAK,SAAS;AAAA,YAChB;AAEA,kBAAM,EAAE,eAAAC,eAAc,IAAI,MAAM,OAAO,IAAS;AAChD,YAAAA,eAAc,QAAQ,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;AAC/D,iBAAK,KAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAChC;AAAA,UACF;AACA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,UAAU;AAClD,kBAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,kBAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,iBAAK,KAAK,KAAK,EAAE,MAAM,CAAC;AACxB;AAAA,UACF;AACA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,UAAU,MAAM,CAAC,GAAG;AAC3D,kBAAM,MAAM,aAAa,MAAM,CAAC,CAAC;AACjC,gBAAI,CAAC,KAAK;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAAG;AAAA,YAAQ;AAChE,iBAAK,KAAK,KAAK,GAAyC;AACxD;AAAA,UACF;AACA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,SAAS;AACjD,kBAAM,SAAS,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AACjD,iBAAK,KAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAChC;AAAA,UACF;AACA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,QAAQ;AAChD,kBAAM,QAAQ,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AAChD,iBAAK,KAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAChC;AAAA,UACF;AACA,cAAI,IAAI,WAAW,YAAY,MAAM,CAAC,GAAG;AACvC,kBAAM,UAAU,MAAM,CAAC,CAAC;AACxB,iBAAK,KAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAChC;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,KAAK,CAAC,CAAC,gBAAgB,QAAQ,YAAY,EAAE,SAAS,MAAM,CAAC,CAAC,KAAK,MAAM,WAAW,GAAG;AACxH,kBAAM,OAAO,MAAM,SAAS;AAC5B,kBAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,mBAAmB,MAAM,CAAC,CAAE,CAAC;AACvE,gBAAI,CAAC,OAAO;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAAG;AAAA,YAAQ;AAClE,kBAAM,UAAU,iBAAiB;AACjC,kBAAM,aAAa,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM,QAAQ,EAAE,WAAW,SAAS,EAAE,IAAI,KAAK;AACtG,kBAAM,KAAK,WAAW;AACtB,kBAAM,eAAe,MAAM,SAAS,iBAAiB,MAAM,WACtD,MAAM;AACL,oBAAM,IAAI,KAAK,aAAa,2BAA2B;AACvD,kBAAI,CAAC,WAAW,CAAC,EAAG,QAAO;AAC3B,kBAAI;AACF,sBAAM,MAAM,KAAK,MAAM,aAAa,GAAG,OAAO,CAAC;AAC/C,sBAAM,OAAO,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAC5C,uBAAO,KAAK,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,OAAO;AAAA,cACrD,QAAQ;AAAE,uBAAO;AAAA,cAAW;AAAA,YAC9B,GAAG,IACH;AAEJ,kBAAM,eAAe,eACjB,aAAa,cAAc,QAC3B,CAAC,EAAE,MAAM,WAAW,aAAa,MAAM,OAAO,GAAG,cAAc;AACnE,gBAAI;AACJ,gBAAI;AACJ,gBAAI,wBAAuC;AAC3C,gBAAI,2BAA0C;AAC9C,kBAAM,oBAAoB,aAAa,qBAAqB,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,IAAI;AACpG,gBAAI,cAAc;AAChB,kBAAI,eAAe;AACnB,oBAAM,YAAY,KAAK,MAAM,QAAQ,MAAM;AAC3C,kBAAI,WAAW,SAAS,GAAG;AACzB,sBAAM,IAAI,aAAa,WAAW,OAAO,EAAE,MAAM,uBAAuB;AACxE,oBAAI,EAAG,gBAAe,SAAS,EAAE,CAAC,GAAG,EAAE;AAAA,cACzC;AACA,+BAAiB,oBAAoB,YAAY;AACjD,kCAAoB,mBAAmB,WAAW,iBAAiB,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,MAAM,IAAI,QAAQ;AACzI,sCAAwB,MAAM,OAAO,oBAAoB,MAAM,IAAI,KAAK;AACxE,yCAA2B,mBAAmB,WAAW,iBAAiB,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,MAAM,IAAI,KAAK;AAAA,YAC/I,OAAO;AACL,+BAAiB,MAAM,OAAO,oBAAoB,MAAM,IAAI,KAAK;AACjE,kBAAI,MAAM,QAAQ;AAChB,sBAAM,KAAK,eAAe,MAAM,MAAM;AACtC,oBAAI,MAAM,eAAgB,mBAAkB;AAAA,cAC9C;AACA,kCAAoB,mBAAmB,WAAW,iBAAiB,QAAQ,KAAM,KAAK,GAAG,GAAG,QAAQ,OAAO,EAAE,CAAC,SAAS,MAAM,IAAI,KAAK;AAAA,YACxI;AACA,kBAAM,MAAM,EAAE,GAAG,OAAO,gBAAgB,YAAY,cAAc,MAAM,UAAU,gBAAgB,aAAa,mBAAmB,iBAAiB,uBAAuB,oBAAoB,yBAAyB;AACvN,iBAAK,KAAK,KAAK,EAAE,IAAI,CAAC;AACtB;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,SAAS,MAAM,WAAW,GAAG;AACpE,gBAAI;AACF,oBAAM,MAAM,MAAM,cAAc,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AAClE,mBAAK,KAAK,KAAK,EAAE,IAAI,CAAC;AAAA,YACxB,SAAS,KAAK;AACZ,mBAAK,KAAK,KAAK,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,YACvC;AACA;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,cAAc,MAAM,WAAW,GAAG;AACzE,gBAAI;AACF,oBAAM,WAAW,MAAM,eAAe,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AACxE,mBAAK,KAAK,KAAK,EAAE,SAAS,CAAC;AAAA,YAC7B,SAAS,MAAM;AACb,mBAAK,KAAK,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;AAAA,YACjC;AACA;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,MAAM,YAAY;AAC5E,kBAAM,WAAW,kBAAkB,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AACrE,iBAAK,KAAK,KAAK,QAAQ;AACvB;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,MAAM,YAAY;AAC5E,kBAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,iCAAqB,mBAAmB,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI;AAC7D,iBAAK,KAAK,KAAK,EAAE,SAAS,KAAK,CAAC;AAChC;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,YAAY,CAAC,MAAM,CAAC,GAAG;AAC/D,kBAAM,QAAQ,MAAM,UAAU,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AAChE,iBAAK,KAAK,KAAK,EAAE,MAAM,CAAC;AACxB;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,cAAc,CAAC,MAAM,CAAC,GAAG;AACjE,gBAAI,SAAkC,CAAC;AACvC,gBAAI;AAAE,uBAAS,KAAK,MAAM,IAAI;AAAA,YAAG,QAAQ;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AAAG;AAAA,YAAQ;AAC9F,gBAAI,CAAC,OAAO,YAAY;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,yBAAyB,CAAC;AAAG;AAAA,YAAQ;AACvF,kBAAM,QAAQ,MAAM,YAAY,mBAAmB,MAAM,CAAC,KAAK,EAAE,GAAG,OAAO,UAAU;AACrF,iBAAK,KAAK,KAAK,EAAE,MAAM,CAAC;AACxB;AAAA,UACF;AAGA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,QAAQ;AAC/C,kBAAM,SAAS,UAAU,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC;AAC3D,gBAAI,CAAC,QAAQ;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAAG;AAAA,YAAQ;AAGnE,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,cACjB,cAAc;AAAA,YAChB,CAAC;AAGD,gBAAI,aAAa;AACjB,kBAAM,EAAE,OAAO,WAAW,IAAI,MAAM,OAAO,OAAO;AAClD,kBAAM,OAAO,WAAW,UAAU,CAAC,WAAW,QAAQ,YAAY,UAAU,IAAI,GAAG;AAAA,cACjF,KAAK;AAAA,cACL,QAAQ;AAAA,cACR,QAAQ;AAAA,cACR,QAAQ;AAAA,YACV,CAAC;AACD,kBAAM,WAAW,CAAC,SAAiB;AACjC,kBAAI,KAAK,KAAK,EAAG,KAAI,MAAM,SAAS,KAAK,QAAQ,UAAU,GAAG,CAAC;AAAA;AAAA,CAAM;AAAA,YACvE;AACA,iBAAK,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AACzC,yBAAW,QAAQ,MAAM,SAAS,EAAE,MAAM,IAAI,EAAG,UAAS,IAAI;AAAA,YAChE,CAAC;AACD,iBAAK,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AACzC,yBAAW,QAAQ,MAAM,SAAS,EAAE,MAAM,IAAI,EAAG,UAAS,IAAI;AAAA,YAChE,CAAC;AAGD,kBAAM,aAAa,mBAAmB,MAAM,CAAC,KAAK,EAAE;AACpD,kBAAM,iBAAiB,CAAC,SAAiB,SAAS,IAAI;AACtD,gBAAI,CAAC,kBAAkB,IAAI,UAAU,EAAG,mBAAkB,IAAI,YAAY,oBAAI,IAAI,CAAC;AACnF,8BAAkB,IAAI,UAAU,EAAG,IAAI,cAAc;AAErD,uBAAW,SAAS,aAAa,IAAI,UAAU,KAAK,CAAC,EAAG,UAAS,MAAM,IAAI;AAE3E,gBAAI,GAAG,SAAS,MAAM;AACpB,kBAAI;AAAE,qBAAK,KAAK;AAAA,cAAG,QAAQ;AAAA,cAAe;AAC1C,gCAAkB,IAAI,UAAU,GAAG,OAAO,cAAc;AAAA,YAC1D,CAAC;AACD;AAAA,UACF;AAAA,QACF;AAMA,YAAI,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,MAAM,eAAe,IAAI,WAAW,OAAO;AAC5E,gBAAM,SAAS,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACzD,gBAAM,eAAe,OAAO,aAAa,IAAI,UAAU,KAAK;AAC5D,gBAAM,YAAY;AAClB,gBAAM,YAAY,mBAAmB,aAAa,WAAW,GAAG,IAAI,eAAe,MAAM,YAAY;AACrG,cAAI;AAEF,kBAAM,eAAe,MAAM,MAAM,GAAG,SAAS,eAAe;AAAA,cAC1D,UAAU;AAAA,cACV,SAAS,EAAE,cAAc,gBAAgB;AAAA,cACzC,QAAQ,YAAY,QAAQ,GAAI;AAAA,YAClC,CAAC;AACD,kBAAM,gBAA0B,CAAC;AACjC,yBAAa,QAAQ,QAAQ,CAAC,KAAK,QAAQ;AACzC,kBAAI,IAAI,YAAY,MAAM,aAAc,eAAc,KAAK,GAAG;AAAA,YAChE,CAAC;AACD,kBAAM,iBAAiB,cAAc,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ,CAAC,KAAK;AAC5E,kBAAM,gBAAgB,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK;AACtD,kBAAM,WAAW,MAAM,aAAa,KAAK;AACzC,kBAAM,YAAY,SAAS,MAAM,gCAAgC,IAAI,CAAC,KACjE,SAAS,MAAM,gCAAgC,IAAI,CAAC,KACpD,cAAc,QAAQ,UAAU,EAAE;AACvC,gBAAI,CAAC,WAAW;AAEd,kBAAI,UAAU,KAAK,EAAE,UAAU,UAAU,CAAC;AAC1C,kBAAI,IAAI;AACR;AAAA,YACF;AAEA,kBAAM,WAAW,IAAI,gBAAgB;AAAA,cACnC,OAAO;AAAA,cACP,WAAW;AAAA,cACX;AAAA,cACA,UAAU;AAAA,YACZ,CAAC;AACD,kBAAM,WAAW,MAAM,MAAM,GAAG,SAAS,eAAe;AAAA,cACtD,QAAQ;AAAA,cACR,SAAS;AAAA,gBACP,gBAAgB;AAAA,gBAChB,UAAU;AAAA,gBACV,cAAc;AAAA,cAChB;AAAA,cACA,MAAM,SAAS,SAAS;AAAA,cACxB,UAAU;AAAA,cACV,QAAQ,YAAY,QAAQ,GAAI;AAAA,YAClC,CAAC;AAID,kBAAM,cAAwB,CAAC;AAC/B,qBAAS,QAAQ,QAAQ,CAAC,KAAK,QAAQ;AACrC,kBAAI,IAAI,YAAY,MAAM,aAAc,aAAY,KAAK,GAAG;AAAA,YAC9D,CAAC;AAED,kBAAM,iBAAiB,YAAY,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;AAC7F,kBAAM,kBAAqD,EAAE,UAAU,UAAU;AACjF,gBAAI,eAAe,SAAS,GAAG;AAC7B,8BAAgB,YAAY,IAAI;AAAA,YAClC,OAAO;AACL,qBAAO,KAAK,gBAAgB,6DAA6D;AAAA,YAC3F;AACA,gBAAI,UAAU,KAAK,eAAe;AAClC,gBAAI,IAAI;AAAA,UACV,SAAS,KAAK;AACZ,mBAAO,KAAK,gBAAgB,6BAA6B,OAAO,GAAG,CAAC,EAAE;AACtE,gBAAI,UAAU,KAAK,EAAE,UAAU,UAAU,CAAC;AAC1C,gBAAI,IAAI;AAAA,UACV;AACA;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,MAAM,aAAa,IAAI,WAAW,OAAO;AAC3E,gBAAM,SAAS,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACzD,gBAAM,YAAY,OAAO,aAAa,IAAI,KAAK,KAAK;AACpD,gBAAM,UAAU,iBAAiB,SAAS;AAC1C,eAAK,KAAK,KAAK,EAAE,SAAS,QAAQ,CAAC;AACnC;AAAA,QACF;AAEA,YAAI,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,WAAW,IAAI,WAAW,OAAO;AACtE,cAAI;AACF,kBAAM,QAAQ,MAAM,eAAe;AACnC,kBAAM,gBAAgB,MAAM,SAAS;AAErC,kBAAM,WAAW,MAAM,IAAI,CAAC,SAAS;AACnC,oBAAM,IAAI;AACV,oBAAM,eAAe,cAAc;AAAA,gBAAK,CAAC,QACvC,IAAI,iBACF,IAAI,aAAa,SAAS,MAAM,KAAK,IAAI,KACzC,IAAI,aAAa,SAAS,MAAM,KAAK,OAAO,GAAG;AAAA,cAEnD;AACA,qBAAO;AAAA,gBACL,GAAG;AAAA,gBACH,UAAW,EAAE,UAAU,KAA4B;AAAA,gBACnD,OAAQ,EAAE,aAAa,KAA4B;AAAA,gBACnD,WAAY,EAAE,SAAS,KAA4B;AAAA,gBACnD,SAAS,cAAc;AAAA,cACzB;AAAA,YACF,CAAC;AACD,iBAAK,KAAK,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,UACpC,SAAS,KAAK;AACZ,iBAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,UACvD;AACA;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,aAAa,IAAI,WAAW,QAAQ;AAC7G,gBAAM,WAAW,mBAAmB,MAAM,CAAC,CAAC;AAC5C,cAAI,SAA+B,CAAC;AACpC,cAAI;AAAE,qBAAS,KAAK,MAAM,IAAI;AAAA,UAAG,QAAQ;AAAE,iBAAK,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AAAG;AAAA,UAAQ;AAC9F,gBAAM,UAAU,OAAO,SAAS,KAAK;AACrC,cAAI,CAAC,SAAS;AAAE,iBAAK,KAAK,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAAG;AAAA,UAAQ;AACvE,cAAI;AACF,kBAAM,WAAW,KAAK,QAAQ,GAAG,YAAY,WAAW;AACxD,gBAAI,WAAW,WAAW,QAAQ,IAAI,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC,IAAI,CAAC;AACrF,kBAAM,QAAQ,MAAM,eAAe;AACnC,kBAAM,OAAO,MAAM,KAAK,OAAK,EAAE,SAAS,QAAQ;AAChD,gBAAI,CAAC,MAAM;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,SAAS,QAAQ,uBAAuB,CAAC;AAAG;AAAA,YAAQ;AACzF,kBAAM,UAAU,KAAK,UAAU,QAAQ,UAAU,EAAE;AAEnD,kBAAM,WAAW,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC,MACxD,EAAE,SAAS,WAAW,EAAE,iBAAiB,EAAE,aAAa,SAAS,MAAM,QAAQ,KAAK,EAAE,aAAa,SAAS,MAAM,WAAW,GAAG,EAAE,IAAI;AACxI,gBAAI,UAAU;AAAE,mBAAK,KAAK,KAAK,EAAE,OAAO,kCAAkC,SAAS,IAAI,IAAI,CAAC;AAAG;AAAA,YAAQ;AACvG,gBAAI,MAAM,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC,MAAwB,EAAE,SAAS,OAAO,IAAI;AACjG,gBAAI,CAAC,KAAK;AAGR,oBAAMC,UAAS,KAAK,MAAM,OAAO,WAAW,GAAG,QAAQ;AACvD,oBAAM,aAAa,MAAMA,QAAO,eAAe,EAAE,KAAK,KAAK,CAAC;AAC5D,oBAAM,mBAAmB,WAAW,KAAK,CAAC,MAAM;AAC9C,sBAAM,MAAM,EAAE,SAAS,4BAA4B,KAAK;AACxD,sBAAM,OAAO,EAAE,SAAS,4BAA4B,KAAK;AACzD,uBAAO,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,OAAO,KAAK,QAAQ;AAAA,cACtE,CAAC;AACD,oBAAMC,QAAO,mBACT,SAAS,OAAO,iBAAiB,QAAQ,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,OACtE;AACJ,oBAAM,OAAQ,KAA4C,UAAU,KAAe;AACnF,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,QAAQ,KAAK,aAAa,QAAQ;AAAA,gBAClC;AAAA,gBACA,MAAAA;AAAA,gBACA,cAAc;AAAA,gBACd,QAAQ,kBAAkB,UAAU,YAAY,YAAY;AAAA,gBAC5D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,cACpC;AACA,kBAAI,MAAM,QAAQ,QAAQ,GAAG;AAAE,yBAAS,KAAK,GAAG;AAAA,cAAG,OAAO;AAAE,2BAAW,CAAC,GAAG;AAAA,cAAG;AAAA,YAChF,OAAO;AACL,kBAAI,eAAe;AAAA,YACrB;AACA,0BAAc,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AACzD,iBAAK,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,UAC7B,SAAS,KAAK;AACZ,iBAAK,KAAK,KAAK,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,UACvC;AACA;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,UAAU,MAAM,CAAC,MAAM,gBAAgB,IAAI,WAAW,OAAO;AAC5E,gBAAM,SAAS,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACzD,gBAAM,UAAU,OAAO,aAAa,IAAI,MAAM,KAAK;AACnD,gBAAMA,QAAO,SAAS,SAAS,EAAE;AACjC,cAAI,CAACA,SAAQA,QAAO,KAAKA,QAAO,OAAO;AACrC,iBAAK,KAAK,KAAK,EAAE,OAAO,eAAe,CAAC;AACxC;AAAA,UACF;AACA,gBAAM,YAAY,MAAM,IAAI,QAAiB,CAAAJ,aAAW;AACtD,kBAAM,OAAO,iBAAiB,EAAE,MAAAI,OAAM,MAAM,YAAY,CAAC;AACzD,iBAAK,KAAK,WAAW,MAAM;AAAE,mBAAK,QAAQ;AAAG,cAAAJ,SAAQ,KAAK;AAAA,YAAG,CAAC;AAC9D,iBAAK,KAAK,SAAS,MAAMA,SAAQ,IAAI,CAAC;AACtC,iBAAK,WAAW,KAAK,MAAM;AAAE,mBAAK,QAAQ;AAAG,cAAAA,SAAQ,IAAI;AAAA,YAAG,CAAC;AAAA,UAC/D,CAAC;AACD,eAAK,KAAK,KAAK,EAAE,MAAAI,OAAM,UAAU,CAAC;AAClC;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,MAAM,UAAU,IAAI,WAAW,QAAQ;AACzE,cAAI;AACF,kBAAM,UAAU,KAAK,MAAM,IAAI;AAI/B,kBAAM,UAAU,QAAQ,YAAY;AACpC,kBAAM,UAAU,QAAQ,OAAO,IAAI,QAAQ,eAAe,EAAE;AAC5D,gBAAI,SAAS;AACX,oBAAM,WAAW,kBAAkB,OAAO;AAC1C,kBAAI,SAAS,cAAc,WAAW,SAAS,cAAc;AAC3D,qBAAK,UAAU,OAAO;AAAA,cACxB;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAA4B;AACpC,eAAK,KAAK,KAAK,EAAE,QAAQ,WAAW,CAAC;AACrC;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,UAAU;AAEzB,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,QAAQ;AAC/C,kBAAM,iBAAiB,KAAK,WAAW;AACvC;AAAA,UACF;AACA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,QAAQ;AAC/C,6BAAiB,KAAK,WAAW;AACjC;AAAA,UACF;AAEA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,WAAW;AACnD,kBAAM,oBAAoB,KAAK,MAAM,WAAW;AAChD;AAAA,UACF;AAEA,cAAI,CAAC,eAAe,KAAK,KAAK,WAAW,EAAG;AAC5C,cAAI,IAAI,WAAW,YAAY,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,GAAG;AACpE,kBAAM,uBAAuB,KAAK,MAAM,CAAC,GAAG,WAAW;AACvD;AAAA,UACF;AACA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,GAAG;AAC7D,kBAAM,mBAAmB,KAAK,MAAM,CAAC,GAAG,WAAW;AACnD;AAAA,UACF;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,cAAc;AAC7B,cAAI,CAAC,eAAe,KAAK,KAAK,WAAW,EAAG;AAC5C,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,SAAS;AAChD,kBAAM,sBAAsB,KAAK,WAAW;AAC5C;AAAA,UACF;AACA,cAAI,IAAI,WAAW,UAAU,MAAM,CAAC,MAAM,UAAU;AAClD,kBAAM,mBAAmB,KAAK,MAAM,aAAa,WAAW;AAC5D;AAAA,UACF;AAAA,QACF;AAGA,YAAI,MAAM,CAAC,MAAM,YAAY;AAC3B,cAAI,CAAC,eAAe,KAAK,KAAK,WAAW,EAAG;AAE5C,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,cAAc;AACrD,wCAA4B,KAAK,WAAW;AAC5C;AAAA,UACF;AACA,cAAI,IAAI,WAAW,SAAS,MAAM,CAAC,MAAM,cAAc;AACrD,kBAAM,4BAA4B,KAAK,MAAM,WAAW;AACxD;AAAA,UACF;AAAA,QACF;AAEA,aAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,CAAC;AAAA,MACvD,SAAS,KAAK;AACZ,eAAO,MAAM,gBAAgB,mBAAmB,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AACtE,aAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,wBAAwB,CAAC;AAAA,MACnE;AACA;AAAA,IACF;AAEA,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,WAAW;AAAA,EACrB,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MACL,IAAI,QAAQ,CAACJ,UAAS,WAAW;AAC/B,aAAO,OAAO,MAAM,aAAa,MAAM;AACrC,eAAO,KAAK,gBAAgB,iCAAiC,IAAI,IAAI,EAAE,KAAK,CAAC;AAC7E,QAAAA,SAAQ,IAAI;AAAA,MACd,CAAC;AACD,aAAO,KAAK,SAAS,MAAM;AAAA,IAC7B,CAAC;AAAA,IACH,MAAM,MACJ,IAAI,QAAQ,CAACA,UAAS,WAAW;AAC/B,aAAO,MAAM,CAAC,QAAS,MAAM,OAAO,GAAG,IAAIA,SAAQ,CAAE;AAAA,IACvD,CAAC;AAAA,EACL;AACF;AAMA,SAAS,eACP,KACA,KACA,OACS;AACT,MAAI,CAAC,OAAO,OAAO,UAAU;AAC3B,SAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,SAAS,gCAAgC,CAAC;AAClF,WAAO;AAAA,EACT;AACA,QAAM,WAAW,IAAI,QAAQ,kBAAkB;AAC/C,MAAI,CAAC,YAAY,aAAa,MAAM,MAAM,UAAU;AAClD,SAAK,KAAK,KAAK,EAAE,OAAO,gBAAgB,SAAS,6CAA6C,CAAC;AAC/F,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMA,eAAe,iBACb,KACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,aAAa,CAAC,GAAG,QAAQ,MAAM,uBAAuB,MAAM,CAAC;AAC9E;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,cAAc,MAAM,WAAW;AAC/C,UAAM,cAAc,IAAI,KAAK,EAAE,IAAI,CAAC,OAAO;AAAA,MACzC,GAAG;AAAA,MACH,aAAa,WAAW,EAAE,QAAQ;AAAA,IACpC,EAAE;AAEF,QAAI,SAAS;AACb,UAAM,KAAK,MAAM,OAAO;AACxB,QAAI,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW;AAC9C,UAAI;AACF,cAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,iCAAwB;AACjE,cAAM,SAAS,MAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ;AAC3E,iBAAS,EAAE,GAAG,QAAQ,YAAY,GAAG,YAAY,UAAU,GAAG,SAAS;AAAA,MACzE,QAAQ;AAAA,MAAmB;AAAA,IAC7B;AAEA,UAAM,wBAAwB,CAAC,EAAE,GAAG,YAAY,GAAG,aAAa,GAAG,UAAU,GAAG;AAEhF,SAAK,KAAK,KAAK,EAAE,aAAa,QAAQ,sBAAsB,CAAC;AAAA,EAC/D,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,SAAS,iBACP,KACA,OACM;AACN,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AAC3B;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,cAAc,MAAM,WAAW;AAC/C,UAAM,OAAO,IAAI,mBAAmB;AACpC,SAAK,KAAK,KAAK,EAAE,KAAK,CAAC;AAAA,EACzB,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,oBACb,KACA,MACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,CAAC;AAC5D;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,gBAAgB,SAAS,kCAAkC,CAAC;AACpG;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,WAAW,OAAO,IAAI;AACvC,MAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ;AACrC,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,SAAS,8CAA8C,CAAC;AAClH;AAAA,EACF;AAGA,MAAI,CAAC,uCAAuC,KAAK,SAAS,GAAG;AAC3D,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,SAAS,sCAAsC,CAAC;AAC7G;AAAA,EACF;AAGA,QAAM,iBAAiB,MAAM,qBAAqB,CAAC,GAAG;AAAA,IACpD,CAAC,MAAM,EAAE,cAAc,aAAa,EAAE,WAAW,UAAU,EAAE,YAAY;AAAA,EAC3E;AACA,MAAI,eAAe;AACjB,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS,cAAc,SAAS,IAAI,MAAM,kCAAkC,cAAc,OAAO;AAAA,MACjG,gBAAgB,cAAc;AAAA,IAChC,CAAC;AACD;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,cAAc,MAAM,WAAW;AAC/C,UAAM,SAAS,MAAM,IAAI,QAAQ,SAAS,WAAW,QAAQ;AAAA,MAC3D,OAAO,CAAC,SAAS,eAAe,SAAS,KAAK,QAAQ,qBAAqB,EAAE,CAAC;AAAA,IAChF,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AAEnB,UAAI,OAAO,UAAU,kBAAkB;AACrC,aAAK,KAAK,KAAK;AAAA,UACb,SAAS;AAAA,UACT,OAAO;AAAA,UACP,SAAS,cAAc,SAAS,IAAI,MAAM;AAAA,UAC1C,OAAO,OAAO;AAAA,QAChB,CAAC;AACD;AAAA,MACF;AACA,YAAM,aAAa,OAAO,OAAO,WAAW,iBAAiB,IAAI,MAAM;AACvE,WAAK,KAAK,YAAY,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,CAAC;AACzG;AAAA,IACF;AAGA,UAAM,yBAAyB,UAAU,MAAM,WAAW;AAC1D,QAAI,wBAAwB,mBAAmB;AAC7C,YAAM,oBAAoB,uBAAuB;AAAA,IACnD;AAEA,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,MACpB,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,uBACb,KACA,SACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,CAAC;AAC5D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,cAAc,MAAM,WAAW;AAC/C,UAAM,SAAS,MAAM,IAAI,WAAW,OAAO;AAE3C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,aAAa,OAAO,OAAO,WAAW,eAAe,IAAI,MAAM;AACrE,WAAK,KAAK,YAAY,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,OAAO,MAAM,CAAC;AACnG;AAAA,IACF;AAGA,UAAM,4BAA4B,UAAU,MAAM,WAAW;AAC7D,QAAI,2BAA2B;AAC7B,YAAM,oBAAoB,0BAA0B,qBAAqB,CAAC;AAAA,IAC5E;AAEA,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,SAAS,OAAO;AAAA,MAChB,iBAAiB,OAAO;AAAA,MACxB,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAEA,eAAe,mBACb,KACA,SACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,CAAC;AAC5D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,cAAc,MAAM,WAAW;AAC/C,UAAM,WAAW,MAAM,IAAI,OAAO,OAAO;AAEzC,QAAI,SAAS,WAAW,GAAG;AACzB,WAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,iBAAiB,SAAS,iCAAiC,OAAO,GAAG,CAAC;AAC9G;AAAA,IACF;AAEA,SAAK,KAAK,KAAK,SAAS,CAAC,CAAC;AAAA,EAC5B,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;AAMA,eAAe,sBACb,KACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,SAAS,8DAA8D,CAAC;AAC5H;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,OAAO,WAAW;AACzC,MAAI,CAAC,UAAU;AACb,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,SAAS,8DAA8D,CAAC;AAC5H;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,iCAAwB;AAC1D,UAAM,QAAQ,MAAM,SAAS,QAAQ;AAKrC,QAAI,CAAC,MAAM,OAAO,WAAW,aAAa,MAAM,SAAS,GAAG;AAC1D,YAAM,iBAAiB,MAAM,CAAC,GAAG;AACjC,UAAI,gBAAgB;AAClB,cAAM,OAAO,WAAW,YAAY;AACpC,cAAM,EAAE,WAAW,KAAK,IAAI,MAAM,OAAO,qBAAoB;AAC7D,aAAK,KAAK;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,MAAM,WAAW,GAAG;AACtB,WAAK,KAAK,KAAK;AAAA,QACb,SAAS;AAAA,QACT,OAAO,CAAC;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAEA,SAAK,KAAK,KAAK,EAAE,SAAS,MAAM,MAAM,CAAC;AAAA,EACzC,SAAS,MAAM;AACb,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,eAAe,mBACb,KACA,MACA,OACA,aACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,0BAA0B,SAAS,kEAAkE,CAAC;AAC9I;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,gBAAgB,SAAS,kCAAkC,CAAC;AACpG;AAAA,EACF;AAEA,QAAM,EAAE,WAAW,IAAI;AACvB,MAAI,CAAC,cAAc,CAAC,WAAW,KAAK,GAAG;AACrC,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,uBAAuB,SAAS,yBAAyB,CAAC;AAClG;AAAA,EACF;AAEA,QAAM,KAAK,MAAM,OAAO;AACxB,MAAI,CAAC,GAAG,YAAY,CAAC,GAAG,aAAa,CAAC,GAAG,QAAQ;AAC/C,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,0BAA0B,SAAS,kEAAkE,CAAC;AAC9I;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,cAAc,eAAe,IAAI,MAAM,OAAO,iCAAwB;AAC9E,UAAM,SAAS,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,WAAW,KAAK,CAAC;AAEhF,UAAM,EAAE,WAAW,KAAK,IAAI,MAAM,OAAO,qBAAoB;AAC7D,UAAM,OAAO,WAAW,WAAW,OAAO;AAC1C,UAAM,OAAO,WAAW,cAAc,OAAO;AAC7C,UAAM,OAAO,WAAW,aAAa,WAAW,KAAK;AACrD,UAAM,OAAO,WAAW,aAAa;AACrC,UAAM,OAAO,WAAW,UAAU;AAClC,SAAK,KAAK;AAIV,QAAI,GAAG,UAAU;AACf,YAAM,OAAO,OAAO,GAAG;AACvB,WAAK,KAAK;AAAA,IACZ;AAEA,UAAM,WAAW,GAAG;AAIpB,QAAI;AACF,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,2BAAkB;AAC3D,YAAM,gBAAiB,MAAM,OAA6C,YAAY;AACtF,sBAAgB,wBAAwB,aAAa;AACrD,aAAO,KAAK,UAAU,IAAI,UAAU,4DAAuD;AAAA,IAC7F,SAAS,GAAG;AACV,aAAO,KAAK,UAAU,IAAI,UAAU,sCAAsC,aAAa,QAAQ,EAAE,UAAU,CAAC,EAAE;AAAA,IAChH;AAEA,UAAM,QAAyF,CAAC;AAIhG,QAAI,iBAAiB;AACrB,QAAI,qBAAqB;AAEzB,UAAM,cAAc,KAAK,aAAa,oBAAoB;AAC1D,UAAM,EAAE,YAAY,SAAS,IAAI,MAAM,OAAO,IAAS;AACvD,QAAI,SAAS,WAAW,GAAG;AACzB,UAAI;AACF,cAAM,EAAE,8BAA8B,IAAI,MAAM,OAAO,iCAAwB;AAC/E,yBAAiB,8BAA8B,aAAa,OAAO,WAAW;AAC9E,eAAO,KAAK,UAAU,IAAI,UAAU,mCAAmC,cAAc,EAAE;AAAA,MACzF,SAAS,GAAG;AACV,eAAO,KAAK,UAAU,IAAI,UAAU,2BAA2B,aAAa,QAAQ,EAAE,UAAU,CAAC,EAAE;AAAA,MACrG;AAEA,UAAI,gBAAgB;AAClB,YAAI;AACF,gBAAM,EAAE,OAAO,YAAY,IAAI,MAAM,OAAO,OAAO;AACnD,gBAAM,KAAK,MAAM;AAAA,YACf;AAAA,YACA,CAAC,WAAW,MAAM,aAAa,MAAM,MAAM,oBAAoB,aAAa;AAAA,YAC5E,EAAE,KAAK,aAAa,QAAQ,MAAM;AAAA,UACpC;AACA,+BAAqB,GAAG,aAAa;AACrC,cAAI,CAAC,oBAAoB;AACvB,mBAAO,KAAK,UAAU,IAAI,UAAU,uCAAuC,GAAG,QAAQ,MAAM,GAAG,MAAM,EAAE;AAAA,UACzG,OAAO;AACL,mBAAO,KAAK,UAAU,IAAI,UAAU,mCAAmC;AAAA,UACzE;AAAA,QACF,SAAS,GAAG;AACV,iBAAO,KAAK,UAAU,IAAI,UAAU,qCAAqC,aAAa,QAAQ,EAAE,UAAU,CAAC,EAAE;AAAA,QAC/G;AAAA,MACF;AAGA,UAAI,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,UAAU;AAC1D,YAAI;AACF,gBAAM,EAAE,wBAAwB,uBAAuB,IAAI,MAAM,OAAO,iCAAwB;AAChG,gBAAM,SAAS,uBAAuB,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,SAAS,EAAE;AACpF,gBAAM,uBAAuB,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,UAAU,MAAM;AACrF,gBAAM,KAAK,EAAE,MAAM,sBAAsB,SAAS,MAAM,UAAU,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;AAClG,iBAAO,KAAK,UAAU,IAAI,UAAU,6BAA6B,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,QAC9G,SAAS,GAAG;AACV,gBAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,gBAAM,KAAK,EAAE,MAAM,sBAAsB,SAAS,OAAO,QAAQ,IAAI,CAAC;AACtE,iBAAO,KAAK,UAAU,IAAI,UAAU,2CAA2C,GAAG,EAAE;AAAA,QACtF;AAGA,cAAM,EAAE,iBAAiB,wBAAwB,UAAU,IAAI,MAAM,OAAO,iCAAwB;AACpG,cAAM,YAAY,UAAU,KAAK;AACjC,cAAM,aAAuB,CAAC;AAC9B,cAAM,YAAsB,CAAC;AAC7B,mBAAW,SAAS,WAAW;AAC7B,cAAI;AACF,kBAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,MAAM,WAAW,QAAQ;AACpF,uBAAW,KAAK,MAAM,SAAS;AAAA,UACjC,SAAS,GAAG;AACV,kBAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,mBAAO,KAAK,UAAU,IAAI,UAAU,mBAAmB,MAAM,SAAS,wBAAwB,GAAG,EAAE;AACnG,sBAAU,KAAK,MAAM,SAAS;AAAA,UAChC;AAAA,QACF;AACA,cAAM,KAAK,EAAE,MAAM,eAAe,SAAS,UAAU,WAAW,GAAG,UAAU,YAAY,GAAI,UAAU,SAAS,IAAI,EAAE,QAAQ,YAAY,UAAU,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,EAAG,CAAC;AAAA,MAC5K;AAGA,UAAI,UAAU;AACZ,YAAI;AACF,gBAAM,EAAE,mCAAmC,IAAI,MAAM,OAAO,iCAAwB;AACpF,gBAAM,kBAAkB,mCAAmC,aAAa,QAAQ;AAChF,gBAAM,KAAK,EAAE,MAAM,wBAAwB,SAAS,MAAM,UAAU,gBAAgB,CAAC;AACrF,iBAAO,KAAK,UAAU,IAAI,UAAU,sBAAsB,gBAAgB,KAAK,IAAI,KAAK,MAAM,EAAE;AAGhG,cAAI,gBAAgB,SAAS,GAAG;AAC9B,gBAAI;AACF,oBAAM,EAAE,OAAO,SAAS,IAAI,MAAM,OAAO,OAAO;AAChD,oBAAM,UAAU,MAAM;AAAA,gBACpB;AAAA,gBACA,CAAC,WAAW,MAAM,aAAa,MAAM,MAAM,oBAAoB,GAAG,eAAe;AAAA,gBACjF,EAAE,KAAK,aAAa,QAAQ,MAAM;AAAA,cACpC;AACA,oBAAM,YAAY,QAAQ,aAAa;AACvC,oBAAM,KAAK,EAAE,MAAM,sBAAsB,SAAS,WAAW,UAAU,gBAAgB,CAAC;AACxF,kBAAI,CAAC,WAAW;AACd,uBAAO,KAAK,UAAU,IAAI,UAAU,kCAAkC,QAAQ,QAAQ,MAAM,QAAQ,MAAM,EAAE;AAAA,cAC9G,OAAO;AACL,uBAAO,KAAK,UAAU,IAAI,UAAU,gBAAgB,gBAAgB,KAAK,IAAI,CAAC,EAAE;AAAA,cAClF;AAAA,YACF,SAAS,GAAG;AACV,oBAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,oBAAM,KAAK,EAAE,MAAM,sBAAsB,SAAS,OAAO,QAAQ,IAAI,CAAC;AACtE,qBAAO,KAAK,UAAU,IAAI,UAAU,gCAAgC,GAAG,EAAE;AAAA,YAC3E;AAAA,UACF;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,gBAAM,KAAK,EAAE,MAAM,wBAAwB,SAAS,OAAO,QAAQ,IAAI,CAAC;AACxE,iBAAO,KAAK,UAAU,IAAI,UAAU,mCAAmC,GAAG,EAAE;AAAA,QAC9E;AAAA,MACF;AAAA,IACF,OAAO;AACL,aAAO,KAAK,UAAU,IAAI,UAAU,+BAA+B,WAAW,8CAAyC;AAAA,IACzH;AAEA,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,UAAU,OAAO;AAAA,MACjB,YAAY,WAAW,KAAK;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,QAAI,IAAI,YAAY,EAAE,SAAS,gBAAgB,GAAG;AAChD,WAAK,KAAK,KAAK;AAAA,QACb,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,mBAAmB,UAAU;AAAA,MACxC,CAAC;AAAA,IACH,OAAO;AACL,WAAK,KAAK,KAAK;AAAA,QACb,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAMA,SAAS,4BACP,KACA,OACM;AACN,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,YAAY,MAAM,CAAC;AACpC;AAAA,EACF;AAEA,QAAM,KAAK,MAAM,OAAO;AACxB,QAAM,OAAO,CAAC,MAA0B,CAAC,IAAI,YAAY,EAAE,SAAS,IAAI,EAAE,MAAM,GAAG,CAAC,IAAI,QAAQ,EAAE,MAAM,EAAE,IAAI;AAE9G,OAAK,KAAK,KAAK;AAAA,IACb,YAAY,CAAC,EAAE,GAAG,YAAY,GAAG,aAAa,GAAG;AAAA,IACjD,WAAW,KAAK,GAAG,SAAS;AAAA,IAC5B,QAAQ,KAAK,GAAG,MAAM;AAAA,IACtB,UAAU,GAAG,YAAY;AAAA,IACzB,UAAU,KAAK,GAAG,QAAQ;AAAA,IAC1B,YAAY,GAAG,cAAc;AAAA,IAC7B,aAAa,CAAC,CAAC,GAAG;AAAA,IAClB,eAAe,CAAC,CAAC,GAAG;AAAA;AAAA,IACpB,aAAa,MAAM,eAAe;AAAA,EACpC,CAAC;AACH;AAEA,eAAe,4BACb,KACA,MACA,OACe;AACf,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,mBAAmB,CAAC;AAC5D;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,gBAAgB,SAAS,kCAAkC,CAAC;AACpG;AAAA,EACF;AAEA,QAAM,EAAE,WAAW,QAAQ,SAAS,IAAI;AAExC,QAAM,WAAW,OAAO,YAAY,MAAM,OAAO,WAAW;AAC5D,MAAI,CAAC,UAAU;AACb,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,iBAAiB,SAAS,uBAAuB,CAAC;AAC1F;AAAA,EACF;AAGA,MAAI;AACF,UAAM,SAAS,MAAM,YAAY,QAAQ;AACzC,QAAI,CAAC,OAAO,OAAO;AACjB,WAAK,KAAK,KAAK;AAAA,QACb,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAGA,UAAM,EAAE,WAAW,KAAK,IAAI,MAAM,OAAO,qBAAoB;AAC7D,UAAM,OAAO,WAAW,WAAW;AAGnC,QAAI,oBAAoB,aAAa,MAAM,OAAO,WAAW,aACxD,OAAO,MAAM,OAAO,iCAAwB,GAAG,YAAY,QAAQ,EACjE,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,EAAE,MAAM,MAAM,EAAE;AAEjD,QAAI,OAAQ,OAAM,OAAO,WAAW,SAAS;AAC7C,QAAI,SAAU,OAAM,OAAO,WAAW,WAAW;AAGjD,QAAI,WAAW,MAAM,OAAO,WAAW;AACvC,QAAI,QAAQ;AACV,UAAI;AACF,cAAM,EAAE,SAAS,IAAI,MAAM,OAAO,iCAAwB;AAC1D,cAAM,QAAQ,MAAM,SAAS,QAAQ;AACrC,cAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAC/C,YAAI,OAAO;AACT,qBAAW,MAAM;AACjB,gBAAM,OAAO,WAAW,WAAW;AAEnC,cAAI,CAAC,qBAAqB,MAAM,WAAW;AACzC,gCAAoB,MAAM;AAAA,UAC5B;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAAqB;AAAA,IAC/B;AAEA,QAAI,kBAAmB,OAAM,OAAO,WAAW,YAAY;AAC3D,SAAK,KAAK;AAEV,SAAK,KAAK,KAAK;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV,OAAO,OAAO,SAAS;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,EACvD;AACF;","names":["resolve","c","writeFileSync","docker","port"]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readApps
|
|
4
|
+
} from "./chunk-JIPAYMOA.js";
|
|
2
5
|
import {
|
|
3
6
|
configureTunnelIngress,
|
|
4
7
|
createDnsRecord,
|
|
@@ -6,12 +9,12 @@ import {
|
|
|
6
9
|
getActiveServiceRoutes,
|
|
7
10
|
getDnsRecords,
|
|
8
11
|
getTunnelHealth
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YXFDB5YX.js";
|
|
10
13
|
import {
|
|
11
14
|
addExternalLabels,
|
|
12
15
|
getServiceDefinition,
|
|
13
16
|
removeExternalLabels
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-AXSHZEB3.js";
|
|
15
18
|
import {
|
|
16
19
|
loadState,
|
|
17
20
|
runRotation,
|
|
@@ -891,55 +894,6 @@ import fs from "fs";
|
|
|
891
894
|
import path from "path";
|
|
892
895
|
import os from "os";
|
|
893
896
|
import { execa } from "execa";
|
|
894
|
-
|
|
895
|
-
// src/services/app-registry.ts
|
|
896
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
897
|
-
import { dirname } from "path";
|
|
898
|
-
function readApps(appsJsonPath) {
|
|
899
|
-
if (!existsSync4(appsJsonPath)) return [];
|
|
900
|
-
try {
|
|
901
|
-
return JSON.parse(readFileSync3(appsJsonPath, "utf-8"));
|
|
902
|
-
} catch {
|
|
903
|
-
return [];
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
function writeApps(appsJsonPath, apps) {
|
|
907
|
-
mkdirSync2(dirname(appsJsonPath), { recursive: true });
|
|
908
|
-
writeFileSync2(appsJsonPath, JSON.stringify(apps, null, 2), "utf-8");
|
|
909
|
-
}
|
|
910
|
-
function addApp(appsJsonPath, entry) {
|
|
911
|
-
const apps = readApps(appsJsonPath);
|
|
912
|
-
if (apps.some((a) => a.name === entry.name)) {
|
|
913
|
-
throw new Error(`App "${entry.name}" already exists`);
|
|
914
|
-
}
|
|
915
|
-
writeApps(appsJsonPath, [...apps, entry]);
|
|
916
|
-
}
|
|
917
|
-
function updateApp(appsJsonPath, name, patch) {
|
|
918
|
-
const apps = readApps(appsJsonPath);
|
|
919
|
-
const idx = apps.findIndex((a) => a.name === name);
|
|
920
|
-
if (idx === -1) throw new Error(`App "${name}" not found`);
|
|
921
|
-
apps[idx] = { ...apps[idx], ...patch };
|
|
922
|
-
writeApps(appsJsonPath, apps);
|
|
923
|
-
}
|
|
924
|
-
function removeApp(appsJsonPath, name) {
|
|
925
|
-
const apps = readApps(appsJsonPath).filter((a) => a.name !== name);
|
|
926
|
-
writeApps(appsJsonPath, apps);
|
|
927
|
-
}
|
|
928
|
-
function readDeployHistory(historyJsonPath) {
|
|
929
|
-
if (!existsSync4(historyJsonPath)) return [];
|
|
930
|
-
try {
|
|
931
|
-
return JSON.parse(readFileSync3(historyJsonPath, "utf-8"));
|
|
932
|
-
} catch {
|
|
933
|
-
return [];
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
function appendDeployHistory(historyJsonPath, entry) {
|
|
937
|
-
const entries = readDeployHistory(historyJsonPath);
|
|
938
|
-
mkdirSync2(dirname(historyJsonPath), { recursive: true });
|
|
939
|
-
writeFileSync2(historyJsonPath, JSON.stringify([...entries, entry], null, 2), "utf-8");
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// src/services/domain-manager.ts
|
|
943
897
|
var DomainManager = class {
|
|
944
898
|
projectName;
|
|
945
899
|
state;
|
|
@@ -1022,7 +976,7 @@ var DomainManager = class {
|
|
|
1022
976
|
let previousIngress = null;
|
|
1023
977
|
try {
|
|
1024
978
|
previousIngress = getActiveServiceRoutes(this.state);
|
|
1025
|
-
const projectDomain = this.state.domain.zoneName;
|
|
979
|
+
const projectDomain = this.state.domain.cloudflare?.zoneName || this.state.domain.name || domain;
|
|
1026
980
|
const builtinRoutes = previousIngress.map((r) => ({ ...r, domain: projectDomain }));
|
|
1027
981
|
const existingExtRoutes = (this.state.domainConnections ?? []).filter((c) => c.appName !== appName).map((c) => ({ subdomain: c.subdomain, containerName: this.resolveContainerName(c.appName), port: c.containerPort, domain: c.domain }));
|
|
1028
982
|
const newRoute = { subdomain, containerName: this.resolveContainerName(appName), port: containerPort, domain };
|
|
@@ -1140,7 +1094,7 @@ var DomainManager = class {
|
|
|
1140
1094
|
const cf = this.state.domain.cloudflare;
|
|
1141
1095
|
try {
|
|
1142
1096
|
const remainingRoutes = getActiveServiceRoutes(this.state);
|
|
1143
|
-
const projectDomain = this.state.domain.zoneName;
|
|
1097
|
+
const projectDomain = this.state.domain.cloudflare?.zoneName || this.state.domain.name || conn.domain;
|
|
1144
1098
|
const builtinRoutes = remainingRoutes.filter((r) => r.subdomain !== conn.subdomain).map((r) => ({ ...r, domain: projectDomain }));
|
|
1145
1099
|
const remainingExtRoutes = (this.state.domainConnections ?? []).filter((c) => c.appName !== appName).map((c) => ({ subdomain: c.subdomain, containerName: this.resolveContainerName(c.appName), port: c.containerPort, domain: c.domain }));
|
|
1146
1100
|
const filteredRoutes = [...builtinRoutes, ...remainingExtRoutes];
|
|
@@ -1167,7 +1121,7 @@ var DomainManager = class {
|
|
|
1167
1121
|
} catch (err) {
|
|
1168
1122
|
try {
|
|
1169
1123
|
const routes = getActiveServiceRoutes(this.state);
|
|
1170
|
-
const projectDomain = this.state.domain.zoneName;
|
|
1124
|
+
const projectDomain = this.state.domain.cloudflare?.zoneName || this.state.domain.name || conn.domain;
|
|
1171
1125
|
const allBuiltin = routes.map((r) => ({ ...r, domain: projectDomain }));
|
|
1172
1126
|
const allExt = (this.state.domainConnections ?? []).map((c) => ({ subdomain: c.subdomain, containerName: this.resolveContainerName(c.appName), port: c.containerPort, domain: c.domain }));
|
|
1173
1127
|
const allRoutes = [...allBuiltin, ...allExt];
|
|
@@ -1382,12 +1336,6 @@ export {
|
|
|
1382
1336
|
restoreBackup,
|
|
1383
1337
|
listBackups,
|
|
1384
1338
|
checkDiskSpace,
|
|
1385
|
-
readApps,
|
|
1386
|
-
addApp,
|
|
1387
|
-
updateApp,
|
|
1388
|
-
removeApp,
|
|
1389
|
-
readDeployHistory,
|
|
1390
|
-
appendDeployHistory,
|
|
1391
1339
|
DomainManager
|
|
1392
1340
|
};
|
|
1393
|
-
//# sourceMappingURL=chunk-
|
|
1341
|
+
//# sourceMappingURL=chunk-YAYXULLO.js.map
|