import { Profile, Components, BuildResult, CeProfileConfig } from './types.js'; /** * Per-key diagnosis used when resolveCrossComponentRefs or resolveVariables * hits its max-pass cap. Each unresolved entry is either part of a cycle * (kind: 'circular', detail = the cycle path A → B → ... → A) or refers * to a key that doesn't exist anywhere in scope (kind: 'missing', * detail = the names that aren't defined). Without this distinction the * raw "hit 10 passes" warning gives users nothing to act on. */ export interface UnresolvedDiagnosis { key: string; value: string; kind: 'circular' | 'missing'; detail: string[]; } /** * Diagnose which unresolved entries are circular vs missing. * * entries — flat map from key → value (the value still contains `${...}` after max passes) * refPattern — regex with a capture group that yields the dependency name (e.g. `redis.HOST`) * * The regex is run with /g; the first capture group of each match is * treated as a dependency. Whether the dependency itself exists in * `entries` distinguishes missing from circular. */ export declare function diagnoseUnresolved(entries: Map, refPattern: RegExp): UnresolvedDiagnosis[]; /** * Format diagnoseUnresolved() output as human-readable warning lines. * Caller already printed the headline ("⚠️ ... hit N passes ..."); we * just append one line per offending key with the cycle path or missing * ref so users have something concrete to grep for in their config. */ export declare function formatUnresolvedDiagnosis(diagnoses: UnresolvedDiagnosis[]): string[]; export declare class EnvironmentBuilder { private envName?; private configDir; private outputPath; private contracts; private envDir; constructor(configDir: string, outputPath: string, envName?: string | undefined, envDir?: string); initialize(): Promise; /** * Build environment from a named profile. * * Components are auto-discovered from env/components/*.env. * Profile JSON files are optional — they provide section overrides per component. * For each component: [default] section + [profileName] section(s) layer on top. */ buildFromProfile(profileName: string, serveMode?: Set | 'all'): Promise; /** * Build environment from an explicit component map. * Writes a single .env file to outputPath. */ buildFromComponents(components: Components, profile?: Profile): Promise; /** * Generate a service's .env file from the resolved variable pool. * Handles both new-format (vars) and legacy (required/optional/secret) contracts. */ generateServiceEnvFile(serviceName: string, systemVars: Record, outputPath?: string, currentEnv?: string, componentPool?: Map>): Promise; /** * Lenient variant of generateServiceEnvFile for defaults-only builds. * Skips vars that can't be resolved instead of leaving gaps. * Used when contract.default is set but no default profile exists. */ generateServiceEnvFileLenient(serviceName: string, systemVars: Record, outputPath: string, currentEnv: string, componentPool?: Map>): Promise; listProfiles(): { name: string; description: string; }[]; /** * Discover all profile names from JSON files in env/profiles/. * Only explicitly defined profiles — not inferred from component section names. */ discoverAllProfileNames(): string[]; /** * Build profiles and generate multi-profile compose output. * * @param envProfile — if set, only write .env.{profile} for this one profile. * If omitted, write .env files for ALL profiles. * The compose file always includes all profiles regardless. */ buildAllProfiles(envProfile?: string, profileSuffixes?: Record, profileConfigs?: Record): Promise; /** * Generate profile.* pseudo-variables — profile-global primitives. * Use these in components/contracts when you want the profile-level * suffix/domain/protocol/name without naming a specific service. * * Keys: name, suffix, domain, protocol. * * Example component usage: * COOKIE_DOMAIN=${profile.domain} * NODE_ENV=${profile.name === 'local' ? ...} // ce doesn't do ternaries, * // so use a per-section [local]/[production] override or pair with defaults */ private generateProfileVars; /** * Generate service.* pseudo-variables for all target contracts. * For each service: host, address, suffix, domain, protocol. * Keys are dotted: "game-server.host", "game-server.address", etc. * * Proxy-aware behavior: when the profile has `proxy: "caddy"` (or "both") * AND a contract has `target.subdomain`, the public address resolves to * the proxy vhost ({subdomain}.{domain}) over https. This is the URL the * browser uses to reach the service through the reverse proxy. * * - host: always the Docker DNS name ({service}{suffix}). Internal * container-to-container calls keep using this — they don't * go through the proxy. * - address: proxy vhost when proxied, else {host}.{domain} (Docker DNS). * This is what NEXT_PUBLIC_* and other browser-facing env * vars should reference. * - protocol: "https" when proxied or when profile.tls is true, else "http". * * Contracts without a subdomain are not proxy-routable; their address and * protocol stay on the Docker DNS form regardless of profile.proxy. */ private generateServiceVars; /** * Resolve profile data and inheritance chain for a given profile name. // ─── Private helpers ──────────────────────────────────────────────────────── /** * Auto-discover all component files from env/components/*.env. */ private discoverComponents; private buildServiceEnvironments; /** * Check if any component file contains a [profileName] INI section. */ private profileSectionExists; /** * Load the `secrets.*` namespace for the current profile. * * Dispatch: * - ce.json `secrets.provider = "doppler"` → delegate to the Doppler * CLI for this profile's config (per-profile `dopplerConfig` * override, else the profile name). * - default ("env-files") → legacy path: read .env.secrets.shared * (age+SOPS encrypted, committed) + .env.secrets.local (personal * overrides, gitignored), decrypting any CENV_ENC[] values via * the embedded vault. * * The legacy env-files path is preserved for projects mid-migration * (and as the default for projects that never opt in to a provider). */ private loadSecretsPool; /** * Pull secrets from Doppler for the given profile. The per-profile * `dopplerConfig` field (in ce.json) chooses the doppler config name; * falls back to the ce profile name when unset (so a `production` ce * profile maps to a `production` doppler config by default). * * For tests, CE_DOPPLER_BINARY can override the binary path (so the * suite can point at a fake script instead of the real `doppler`). */ private loadSecretsFromDoppler; /** * Load .env.shared (team) from env/ root and decrypt vault entries. * Used for legacy format. */ private loadSharedFiles; /** * New format: Load components as Map>. * No NAMESPACE prefixing — component filename IS the namespace. * Also loads secrets as the reserved "secrets" component. */ private loadScopedComponentPool; /** * Resolve ${secrets.KEY} references within all components. */ private resolveSecretsInComponents; /** * Resolve cross-component references like ${database.HOST} in component values. */ private resolveCrossComponentRefs; /** * Flatten a scoped component pool into a flat key→value map. */ private flattenComponentPool; /** * Rebuild the scoped componentPool from the resolved flat pool. * This ensures validation sees fully resolved values (e.g., ${service.*} expanded). */ private rebuildComponentPool; /** * Legacy: load components into flat NAMESPACE-prefixed pool. */ private loadFlatComponentPool; private loadProfileWithInheritance; /** * Legacy: Load a component config section with NAMESPACE prefixing. */ private loadComponentConfig; /** * New format: Load a component config section WITHOUT NAMESPACE prefixing. * Keys are stored as-is from the INI section. NAMESPACE directive is ignored. */ private loadComponentConfigRaw; private extractQuotedValues; private loadEnvFile; private resolveVariables; /** * Deep-resolve ${...} references in an arbitrary object (config, profileOverrides, etc.) * using the resolved variable pool. */ private resolveConfigValues; private validateAllContracts; private formatEnvValue; private generateDockerCompose; private shouldIncludeService; private processServiceConfig; private evaluateCondition; private substituteVariables; /** * Compute docker-compose entries for a SINGLE profile without writing * .env files, running strict validation, or generating proxy configs. * Returns entries grouped by compose file path, ready to merge into * buildServiceEnvironments' composeGroups. * * Purpose: `env:build ` builds .env files only for that profile, * but the docker-compose.yml must include EVERY profile's services so * `docker compose --profile up` works regardless of which profile * was last built. This helper produces the other profiles' entries. * * Best-effort — unresolvable vars get their defaults (or are dropped), * not errors. The user is building a different profile and shouldn't * fail just because some OTHER profile has a missing var. */ private collectComposeEntriesForOtherProfile; } //# sourceMappingURL=builder.d.ts.map