{"version":3,"file":"yaml-config.cjs","names":["requireModule","path","nodeFs","AdaptiveSampler","AlwaysSampler","NeverSampler","RandomSampler"],"sources":["../src/yaml-config.ts"],"sourcesContent":["/**\n * YAML configuration loader for autotel\n *\n * Supports:\n * - Auto-discovery of autotel.yaml in cwd\n * - AUTOTEL_CONFIG_FILE env var override\n * - Environment variable substitution: ${env:VAR} and ${env:VAR:-default}\n *\n * @example Auto-discovery\n * ```yaml\n * # autotel.yaml in project root\n * service:\n *   name: my-service\n * exporter:\n *   endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}\n * ```\n *\n * @example Explicit path\n * ```bash\n * AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts\n * ```\n */\n\n// namespace import for browser-bundler compat — see node-require.ts\nimport * as nodeFs from 'node:fs';\nimport path from 'node:path';\nimport type { AutotelConfig, OtlpSignal } from './init';\nimport {\n  AdaptiveSampler,\n  AlwaysSampler,\n  NeverSampler,\n  RandomSampler,\n  type SamplingPreset,\n} from './sampling';\n\n/**\n * Lazy-load yaml parser (optional peer dependency)\n * Only loads when a YAML config file is actually found\n */\nimport { requireModule } from './node-require';\n\nfunction loadYamlParser(): (content: string) => unknown {\n  try {\n    const mod = requireModule<{ parse: (content: string) => unknown }>('yaml');\n    return mod.parse;\n  } catch {\n    throw new Error('YAML parser not found. Install with: pnpm add yaml');\n  }\n}\n\n/**\n * YAML config structure\n * Maps to AutotelConfig with user-friendly naming\n */\nexport interface YamlConfig {\n  service?: {\n    name?: string;\n    version?: string;\n    environment?: string;\n  };\n  exporter?: {\n    endpoint?: string;\n    protocol?: 'http' | 'grpc';\n    headers?: Record<string, string>;\n    destinations?: Array<{\n      endpoint: string;\n      protocol?: 'http' | 'grpc';\n      headers?: Record<string, string>;\n      signals?: OtlpSignal[];\n    }>;\n  };\n  resource?: Record<string, string | number | boolean>;\n  sampling?: {\n    preset?: SamplingPreset;\n    type?: 'adaptive' | 'always_on' | 'always_off' | 'ratio';\n    ratio?: number;\n    baseline_rate?: number;\n    always_sample_errors?: boolean;\n    always_sample_slow?: boolean;\n    slow_threshold_ms?: number;\n  };\n  autoInstrumentations?: string[] | Record<string, { enabled?: boolean }>;\n  debug?: boolean;\n}\n\n/**\n * Environment variable substitution regex\n * Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default}\n */\nconst ENV_VAR_PATTERN = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g;\n\n/**\n * Substitute ${env:VAR} and ${env:VAR:-default} in a string\n *\n * @param value - String potentially containing env var references\n * @returns String with env vars substituted\n *\n * @example\n * substituteEnvVars('${env:NODE_ENV:-development}')\n * // Returns 'production' if NODE_ENV=production, else 'development'\n */\nfunction substituteEnvVars(value: string): string {\n  return value.replaceAll(\n    ENV_VAR_PATTERN,\n    (_match, varName: string, defaultValue?: string) => {\n      const envValue = process.env[varName];\n      if (envValue !== undefined) return envValue;\n      if (defaultValue !== undefined) return defaultValue;\n      console.warn(\n        `[autotel] Environment variable ${varName} not set and no default provided`,\n      );\n      return '';\n    },\n  );\n}\n\n/**\n * Recursively substitute env vars in an object\n *\n * @param obj - Object to process\n * @returns Object with all string values having env vars substituted\n */\nfunction substituteEnvVarsDeep(obj: unknown): unknown {\n  if (typeof obj === 'string') {\n    return substituteEnvVars(obj);\n  }\n  if (Array.isArray(obj)) {\n    return obj.map((item) => substituteEnvVarsDeep(item));\n  }\n  if (obj && typeof obj === 'object') {\n    const result: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(obj)) {\n      result[key] = substituteEnvVarsDeep(value);\n    }\n    return result;\n  }\n  return obj;\n}\n\n/**\n * Find YAML config file path\n *\n * Priority:\n * 1. AUTOTEL_CONFIG_FILE env var (explicit path)\n * 2. autotel.yaml in cwd (convention)\n * 3. autotel.yml in cwd (alternative extension)\n *\n * @returns File path if found, null otherwise\n */\nfunction findConfigFile(): string | null {\n  // Check env var first (explicit takes priority)\n  const envPath = process.env.AUTOTEL_CONFIG_FILE;\n  if (envPath) {\n    const resolved = path.resolve(envPath);\n    if (nodeFs.existsSync(resolved)) return resolved;\n    console.warn(`[autotel] Config file not found: ${envPath}`);\n    return null;\n  }\n\n  // Auto-discover autotel.yaml in cwd\n  const conventionPath = path.resolve(process.cwd(), 'autotel.yaml');\n  if (nodeFs.existsSync(conventionPath)) return conventionPath;\n\n  // Also check .yml extension\n  const altPath = path.resolve(process.cwd(), 'autotel.yml');\n  if (nodeFs.existsSync(altPath)) return altPath;\n\n  return null;\n}\n\n/**\n * Convert YAML config structure to AutotelConfig\n *\n * @param yaml - Parsed and env-substituted YAML config\n * @returns Partial AutotelConfig ready for merging\n */\nfunction yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {\n  const config: Partial<AutotelConfig> = {};\n\n  // Service configuration\n  if (yaml.service?.name) config.service = yaml.service.name;\n  if (yaml.service?.version) config.version = yaml.service.version;\n  if (yaml.service?.environment) config.environment = yaml.service.environment;\n\n  // Exporter configuration\n  if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;\n  if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;\n  if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;\n  if (yaml.exporter?.destinations) {\n    config.destinations = yaml.exporter.destinations;\n  }\n\n  // Resource attributes (flattened)\n  if (yaml.resource) config.resourceAttributes = yaml.resource;\n\n  // Integrations\n  if (yaml.autoInstrumentations)\n    config.autoInstrumentations = yaml.autoInstrumentations;\n\n  // Debug mode\n  if (yaml.debug !== undefined) config.debug = yaml.debug;\n\n  // Sampling configuration\n  if (yaml.sampling?.preset) {\n    warnOnIgnoredPresetOverrides(yaml.sampling);\n    config.sampling = yaml.sampling.preset;\n  } else {\n    const sampler = createSamplerFromYaml(yaml.sampling);\n    if (sampler) config.sampler = sampler;\n  }\n\n  return config;\n}\n\nfunction createSamplerFromYaml(\n  sampling?: YamlConfig['sampling'],\n): AutotelConfig['sampler'] {\n  if (!sampling) return undefined;\n  if (sampling.preset) return undefined;\n\n  const type = sampling.type ?? 'adaptive';\n\n  try {\n    switch (type) {\n      case 'adaptive': {\n        return new AdaptiveSampler({\n          baselineSampleRate: sampling.baseline_rate,\n          alwaysSampleErrors: sampling.always_sample_errors,\n          alwaysSampleSlow: sampling.always_sample_slow,\n          slowThresholdMs: sampling.slow_threshold_ms,\n        });\n      }\n      case 'always_on': {\n        return new AlwaysSampler();\n      }\n      case 'always_off': {\n        return new NeverSampler();\n      }\n      case 'ratio': {\n        if (sampling.ratio === undefined) {\n          console.warn(\n            '[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler.',\n          );\n          return new AdaptiveSampler();\n        }\n        return new RandomSampler(sampling.ratio);\n      }\n      default: {\n        console.warn(\n          `[autotel] Unknown sampling type \"${type}\" in YAML config. Falling back to defaults.`,\n        );\n        return undefined;\n      }\n    }\n  } catch (error) {\n    console.warn(\n      `[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`,\n    );\n    return undefined;\n  }\n}\n\nfunction warnOnIgnoredPresetOverrides(\n  sampling: NonNullable<YamlConfig['sampling']>,\n): void {\n  const ignoredFields = [\n    'type',\n    'ratio',\n    'baseline_rate',\n    'always_sample_errors',\n    'always_sample_slow',\n    'slow_threshold_ms',\n  ].filter((field) => sampling[field as keyof typeof sampling] !== undefined);\n\n  if (ignoredFields.length === 0) {\n    return;\n  }\n\n  console.warn(\n    `[autotel] sampling.preset=\"${sampling.preset}\" ignores these YAML fields: ${ignoredFields.join(', ')}. ` +\n      'Use the programmatic API with sampler or samplingPresets.*(...) for tuned presets.',\n  );\n}\n\n/**\n * Load and parse YAML config file (auto-discovery)\n *\n * Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE.\n * Returns null if no config file found (not an error - YAML config is optional).\n *\n * @returns Partial AutotelConfig or null if no config file found\n *\n * @example\n * const yamlConfig = loadYamlConfig();\n * if (yamlConfig) {\n *   init({ ...yamlConfig, debug: true });\n * }\n */\nexport function loadYamlConfig(): Partial<AutotelConfig> | null {\n  const filePath = findConfigFile();\n  if (!filePath) return null;\n\n  try {\n    const content = nodeFs.readFileSync(filePath, 'utf8');\n    const parseYaml = loadYamlParser();\n    const rawYaml = parseYaml(content) as YamlConfig;\n    const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n    return yamlToAutotelConfig(substituted);\n  } catch (error) {\n    console.error(\n      `[autotel] Failed to load YAML config from ${filePath}:`,\n      error,\n    );\n    return null;\n  }\n}\n\n/**\n * Load YAML config from a specific file path\n *\n * Unlike loadYamlConfig(), this throws if the file cannot be read.\n *\n * @param filePath - Path to YAML config file\n * @returns Partial AutotelConfig\n * @throws Error if file cannot be read or parsed\n *\n * @example\n * import { loadYamlConfigFromFile } from 'autotel/yaml';\n * import { init } from 'autotel';\n *\n * const config = loadYamlConfigFromFile('./config/otel.yaml');\n * init({ ...config, debug: true });\n */\nexport function loadYamlConfigFromFile(\n  filePath: string,\n): Partial<AutotelConfig> {\n  const resolved = path.resolve(filePath);\n  const content = nodeFs.readFileSync(resolved, 'utf8');\n  const parseYaml = loadYamlParser();\n  const rawYaml = parseYaml(content) as YamlConfig;\n  const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n  return yamlToAutotelConfig(substituted);\n}\n\n/**\n * Check if a YAML config file exists (without loading it)\n *\n * @returns true if a config file would be found by loadYamlConfig()\n */\nexport function hasYamlConfig(): boolean {\n  return findConfigFile() !== null;\n}\n"],"mappings":"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"}