/* RFC-0752: Shared helpers for subdomain commands — zone ID resolution, account subdomain derivation, and environment variable resolution. Do not implement command logic — that lives in the individual command files. RFC-0752: initial shared helpers — resolveZoneId, resolveAccountSubdomain, resolveSubdomainEnv. RFC-1325: provisional workers.dev as first-class site host Single host predicate module in werkstatt-shared; provisional registration derives dev/alt/main workers.dev channels from the account subdomain; first dev deploy auto-protects provisional hosts; provisional main responses carry noindex; dns-aid skips provisional sites; site.domain.adopt cuts over to a custom domain. */ import type { SystemConfig, ServiceEntry, ServicesRegistry, } from "@warpgogol/werkstatt-engine/schemas"; import { filterEnv, sourceDotenv } from "../leitstand/adapters/cloudflare-workers.ts"; import { workersDevHost } from "@warpgogol/werkstatt-shared/middleware/site-host"; export function flagString( input: { flags: Record }, key: string, ): string | undefined { const v = input.flags[key]; return typeof v === "string" ? v : undefined; } function extractDomainFromUrl(url: string): string | null { try { const parsed = new URL(url); return parsed.hostname; } catch { return null; } } export function resolveZoneId(systems: SystemConfig[], zoneDomain: string): string { for (const system of systems) { if (!system.deployment) continue; const mainDomain = extractDomainFromUrl(system.deployment.channels.main.url); if (mainDomain === zoneDomain) { if (!system.cloudflareZoneId) { throw new Error( `[subdomain] System '${system.id}' matches zone '${zoneDomain}' but has no cloudflareZoneId. Add it to system-config.yaml.`, ); } return system.cloudflareZoneId; } } throw new Error( `[subdomain] No system found matching zone '${zoneDomain}' in system-config.yaml files.`, ); } export function resolveService(registry: ServicesRegistry, serviceId: string): ServiceEntry { const service = registry.services.find((s: ServiceEntry) => s.id === serviceId); if (!service) { const available = registry.services.map((s: ServiceEntry) => s.id).join(", "); throw new Error( `[subdomain] Service '${serviceId}' not found in services registry. Available services: ${available}`, ); } return service; } function extractAccountFromWorkersDevUrl( url: string | undefined, workerName: string, ): string | null { if (!url) return null; try { const parsed = new URL(url); const parts = parsed.hostname.split("."); if ( parts.length === 4 && parts[0] === workerName && parts[2] === "workers" && parts[3] === "dev" ) { return parts[1]; } return null; } catch { return null; } } export function resolveAccountSubdomain( service: ServiceEntry, env: Record, ): string { const fromUrl = extractAccountFromWorkersDevUrl(service.workersDevUrl, service.workerName); if (fromUrl) return fromUrl; const fromEnv = env["CLOUDFLARE_ACCOUNT_ID"]; if (fromEnv) return fromEnv; throw new Error( `[subdomain] Cannot resolve subdomain for service '${service.id}'. ` + `Set workersDevUrl in the registry service entry or CLOUDFLARE_ACCOUNT_ID in the environment.`, ); } export async function resolveSubdomainEnv( secretsFilePath?: string, ): Promise> { const secretsEnv = secretsFilePath ? await sourceDotenv(secretsFilePath) : {}; return { ...filterEnv(process.env), ...secretsEnv }; } export function buildCnameContent(workerName: string, account: string): string { return workersDevHost(workerName, account); } export function buildRoutePattern(domain: string): string { return `${domain}/*`; }