@utilarium/cardigantime 0.0.24-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +65 -0
  2. package/README.md +398 -0
  3. package/dist/cardigantime.cjs +2169 -0
  4. package/dist/cardigantime.cjs.map +1 -0
  5. package/dist/cardigantime.d.ts +92 -0
  6. package/dist/cardigantime.js +198 -0
  7. package/dist/cardigantime.js.map +1 -0
  8. package/dist/config/executable-security.d.ts +32 -0
  9. package/dist/config/format-detector.d.ts +59 -0
  10. package/dist/configure.d.ts +55 -0
  11. package/dist/configure.js +125 -0
  12. package/dist/configure.js.map +1 -0
  13. package/dist/constants.d.ts +25 -0
  14. package/dist/constants.js +38 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/discovery/discoverer.d.ts +62 -0
  17. package/dist/discovery/hierarchical-modes.d.ts +64 -0
  18. package/dist/discovery/index.d.ts +15 -0
  19. package/dist/discovery/patterns.d.ts +77 -0
  20. package/dist/discovery/root-detection.d.ts +100 -0
  21. package/dist/discovery/traversal-security.d.ts +106 -0
  22. package/dist/env/errors.d.ts +18 -0
  23. package/dist/env/index.d.ts +7 -0
  24. package/dist/env/naming.d.ts +38 -0
  25. package/dist/env/parser.d.ts +61 -0
  26. package/dist/env/reader.d.ts +45 -0
  27. package/dist/env/resolver.d.ts +25 -0
  28. package/dist/env/schema-utils.d.ts +33 -0
  29. package/dist/env/types.d.ts +43 -0
  30. package/dist/error/ArgumentError.d.ts +31 -0
  31. package/dist/error/ArgumentError.js +48 -0
  32. package/dist/error/ArgumentError.js.map +1 -0
  33. package/dist/error/ConfigParseError.d.ts +26 -0
  34. package/dist/error/ConfigurationError.d.ts +21 -0
  35. package/dist/error/ConfigurationError.js +46 -0
  36. package/dist/error/ConfigurationError.js.map +1 -0
  37. package/dist/error/FileSystemError.d.ts +30 -0
  38. package/dist/error/FileSystemError.js +58 -0
  39. package/dist/error/FileSystemError.js.map +1 -0
  40. package/dist/error/index.d.ts +4 -0
  41. package/dist/mcp/discovery.d.ts +105 -0
  42. package/dist/mcp/errors.d.ts +75 -0
  43. package/dist/mcp/index.d.ts +22 -0
  44. package/dist/mcp/integration.d.ts +184 -0
  45. package/dist/mcp/parser.d.ts +141 -0
  46. package/dist/mcp/resolver.d.ts +165 -0
  47. package/dist/mcp/tools/check-config-types.d.ts +208 -0
  48. package/dist/mcp/tools/check-config.d.ts +85 -0
  49. package/dist/mcp/tools/index.d.ts +12 -0
  50. package/dist/mcp/types.d.ts +210 -0
  51. package/dist/parsers/index.d.ts +25 -0
  52. package/dist/parsers/javascript-parser.d.ts +12 -0
  53. package/dist/parsers/json-parser.d.ts +6 -0
  54. package/dist/parsers/typescript-parser.d.ts +15 -0
  55. package/dist/parsers/yaml-parser.d.ts +6 -0
  56. package/dist/read.d.ts +56 -0
  57. package/dist/read.js +653 -0
  58. package/dist/read.js.map +1 -0
  59. package/dist/security/audit-logger.d.ts +135 -0
  60. package/dist/security/cli-validator.d.ts +73 -0
  61. package/dist/security/config-validator.d.ts +95 -0
  62. package/dist/security/defaults.d.ts +17 -0
  63. package/dist/security/index.d.ts +14 -0
  64. package/dist/security/numeric-guard.d.ts +111 -0
  65. package/dist/security/path-guard.d.ts +53 -0
  66. package/dist/security/profiles.d.ts +127 -0
  67. package/dist/security/security-validator.d.ts +109 -0
  68. package/dist/security/string-guard.d.ts +92 -0
  69. package/dist/security/types.d.ts +126 -0
  70. package/dist/security/zod-secure-enum.d.ts +20 -0
  71. package/dist/security/zod-secure-number.d.ts +39 -0
  72. package/dist/security/zod-secure-path.d.ts +24 -0
  73. package/dist/security/zod-secure-string.d.ts +38 -0
  74. package/dist/types.d.ts +584 -0
  75. package/dist/types.js +56 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/util/hierarchical.d.ts +136 -0
  78. package/dist/util/hierarchical.js +436 -0
  79. package/dist/util/hierarchical.js.map +1 -0
  80. package/dist/util/schema-defaults.d.ts +80 -0
  81. package/dist/util/schema-defaults.js +118 -0
  82. package/dist/util/schema-defaults.js.map +1 -0
  83. package/dist/util/storage.d.ts +31 -0
  84. package/dist/util/storage.js +154 -0
  85. package/dist/util/storage.js.map +1 -0
  86. package/dist/validate.d.ts +113 -0
  87. package/dist/validate.js +260 -0
  88. package/dist/validate.js.map +1 -0
  89. package/package.json +84 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sources":["../src/types.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { ZodObject } from \"zod\";\n\nimport { z } from \"zod\";\nimport { SecurityValidationConfig } from \"./security/types\";\n\n// Re-export MCP types for convenience\nexport type {\n MCPConfigSource,\n FileConfigSource,\n ConfigSource,\n ResolvedConfig,\n MCPInvocationContext,\n} from \"./mcp/types\";\n\n/**\n * Available features that can be enabled in Cardigantime.\n * Currently supports:\n * - 'config': Configuration file reading and validation\n * - 'hierarchical': Hierarchical configuration discovery and layering\n */\nexport type Feature = 'config' | 'hierarchical';\n\n/**\n * Supported configuration file formats.\n * \n * - 'yaml': YAML format (.yaml, .yml)\n * - 'json': JSON format (.json)\n * - 'javascript': JavaScript module (.js, .mjs, .cjs)\n * - 'typescript': TypeScript module (.ts, .mts, .cts)\n */\nexport enum ConfigFormat {\n YAML = 'yaml',\n JSON = 'json',\n JavaScript = 'javascript',\n TypeScript = 'typescript'\n}\n\n/**\n * Interface for format-specific configuration parsers.\n * Each parser is responsible for loading and parsing configuration from a specific format.\n * \n * @template T - The type of the parsed configuration object\n */\nexport interface ConfigParser<T = unknown> {\n /** The format this parser handles */\n format: ConfigFormat;\n /** File extensions this parser supports (e.g., ['.yaml', '.yml']) */\n extensions: string[];\n /** \n * Parses configuration content from a file.\n * \n * @param content - The raw file content as a string\n * @param filePath - The absolute path to the configuration file\n * @returns Promise resolving to the parsed configuration object\n * @throws {Error} When parsing fails or content is invalid\n */\n parse(content: string, filePath: string): Promise<T>;\n}\n\n/**\n * Metadata about where a configuration value came from.\n * Used for tracking configuration sources and debugging.\n * \n * @deprecated Use FileConfigSource from ./mcp/types instead.\n * This type is kept for backward compatibility and will be removed in a future version.\n */\nexport interface LegacyConfigSource {\n /** The format of the configuration file */\n format: ConfigFormat;\n /** Absolute path to the configuration file */\n filePath: string;\n /** The parsed configuration content */\n content: unknown;\n /** Timestamp when the configuration was loaded */\n loadedAt: Date;\n}\n\n/**\n * Defines how array fields should be merged in hierarchical configurations.\n * \n * - 'override': Higher precedence arrays completely replace lower precedence arrays (default)\n * - 'append': Higher precedence array elements are appended to lower precedence arrays\n * - 'prepend': Higher precedence array elements are prepended to lower precedence arrays\n */\nexport type ArrayOverlapMode = 'override' | 'append' | 'prepend';\n\n/**\n * Configuration for how fields should be merged in hierarchical configurations.\n * Maps field names (using dot notation) to their overlap behavior.\n * \n * @example\n * ```typescript\n * const fieldOverlaps: FieldOverlapOptions = {\n * 'features': 'append', // features arrays will be combined by appending\n * 'api.endpoints': 'prepend', // nested endpoint arrays will be combined by prepending\n * 'excludePatterns': 'override' // excludePatterns arrays will replace each other (default behavior)\n * };\n * ```\n */\nexport interface FieldOverlapOptions {\n [fieldPath: string]: ArrayOverlapMode;\n}\n\n/**\n * Configuration for resolving relative paths in configuration values.\n * Paths specified in these fields will be resolved relative to the configuration file's directory.\n */\nexport interface PathResolutionOptions {\n /** Array of field names (using dot notation) that contain paths to be resolved */\n pathFields?: string[];\n /** Array of field names whose array elements should all be resolved as paths */\n resolvePathArray?: string[];\n}\n\n/**\n * Default configuration options for Cardigantime.\n * These define the basic behavior of configuration loading.\n */\nexport interface DefaultOptions {\n /** Directory path where configuration files are located */\n configDirectory: string;\n /** Name of the configuration file (e.g., 'config.yaml', 'app.yml') */\n configFile: string;\n /** Whether the configuration directory must exist. If true, throws error if directory doesn't exist */\n isRequired: boolean;\n /** File encoding for reading configuration files (e.g., 'utf8', 'ascii') */\n encoding: string;\n /** Configuration for resolving relative paths in configuration values */\n pathResolution?: PathResolutionOptions;\n /** \n * Configuration for how array fields should be merged in hierarchical mode.\n * Only applies when the 'hierarchical' feature is enabled.\n * If not specified, all arrays use 'override' behavior (default).\n */\n fieldOverlaps?: FieldOverlapOptions;\n /** \n * Security validation configuration (optional, uses development profile by default).\n * Enable security features to validate CLI arguments and config file values.\n */\n security?: Partial<SecurityValidationConfig>;\n /**\n * Optional source metadata for tracking where configuration came from.\n * Populated automatically when configuration is loaded.\n * \n * @deprecated Use the new ConfigSource union type from ./mcp/types instead.\n */\n source?: LegacyConfigSource;\n /**\n * Allow executable configuration files (JavaScript/TypeScript).\n * \n * **SECURITY WARNING**: Executable configs run with full Node.js permissions\n * in the same process as your application. Only enable this if you trust\n * the configuration files being loaded.\n * \n * When disabled (default), JavaScript and TypeScript config files will be\n * ignored with a warning message.\n * \n * @default false\n */\n allowExecutableConfig?: boolean;\n}\n\n/**\n * Complete options object passed to Cardigantime functions.\n * Combines defaults, features, schema shape, and logger.\n * \n * @template T - The Zod schema shape type for configuration validation\n */\nexport interface Options<T extends z.ZodRawShape> {\n /** Default configuration options */\n defaults: DefaultOptions,\n /** Array of enabled features */\n features: Feature[],\n /** Zod schema shape for validating user configuration */\n configShape: T;\n /** Logger instance for debugging and error reporting */\n logger: Logger;\n}\n\n/**\n * Logger interface for Cardigantime's internal logging.\n * Compatible with popular logging libraries like Winston, Bunyan, etc.\n */\nexport interface Logger {\n /** Debug-level logging for detailed troubleshooting information */\n debug: (message: string, ...args: any[]) => void;\n /** Info-level logging for general information */\n info: (message: string, ...args: any[]) => void;\n /** Warning-level logging for non-critical issues */\n warn: (message: string, ...args: any[]) => void;\n /** Error-level logging for critical problems */\n error: (message: string, ...args: any[]) => void;\n /** Verbose-level logging for extensive detail */\n verbose: (message: string, ...args: any[]) => void;\n /** Silly-level logging for maximum detail */\n silly: (message: string, ...args: any[]) => void;\n}\n\n/**\n * Main Cardigantime interface providing configuration management functionality.\n * \n * @template T - The Zod schema shape type for configuration validation\n */\nexport interface Cardigantime<T extends z.ZodRawShape> {\n /** \n * Adds Cardigantime's CLI options to a Commander.js command.\n * This includes options like --config-directory for runtime config path overrides.\n */\n configure: (command: Command) => Promise<Command>;\n /** Sets a custom logger for debugging and error reporting */\n setLogger: (logger: Logger) => void;\n /** \n * Reads configuration from files and merges with CLI arguments.\n * Returns a fully typed configuration object.\n */\n read: (args: Args) => Promise<z.infer<ZodObject<T & typeof ConfigSchema.shape>>>;\n /** \n * Validates the merged configuration against the Zod schema.\n * Throws ConfigurationError if validation fails.\n */\n validate: (config: z.infer<ZodObject<T & typeof ConfigSchema.shape>>) => Promise<void>;\n /** \n * Generates a configuration file with default values in the specified directory.\n * Creates the directory if it doesn't exist and writes a config file with all default values populated.\n */\n generateConfig: (configDirectory?: string) => Promise<void>;\n /** \n * Checks and displays the resolved configuration with detailed source tracking.\n * Shows which file and hierarchical level contributed each configuration value in a git blame-like format.\n */\n checkConfig: (args: Args) => Promise<void>;\n}\n\n/**\n * Parsed command-line arguments object, typically from Commander.js opts().\n * Keys correspond to CLI option names with values from user input.\n */\nexport interface Args {\n [key: string]: any;\n}\n\n/**\n * Base Zod schema for core Cardigantime configuration.\n * Contains the minimum required configuration fields.\n */\nexport const ConfigSchema = z.object({\n /** The resolved configuration directory path */\n configDirectory: z.string(),\n /** Array of all directory paths that were discovered during hierarchical search */\n discoveredConfigDirs: z.array(z.string()),\n /** Array of directory paths that actually contained valid configuration files */\n resolvedConfigDirs: z.array(z.string()),\n});\n\n/**\n * Base configuration type derived from the core schema.\n */\nexport type Config = z.infer<typeof ConfigSchema>;\n\n// ============================================================================\n// Configuration Discovery Types\n// ============================================================================\n\n/**\n * Defines a configuration file naming pattern.\n * Used to discover configuration files in various standard locations.\n * \n * Patterns support placeholders:\n * - `{app}` - The application name (e.g., 'protokoll', 'myapp')\n * - `{ext}` - The file extension (e.g., 'yaml', 'json', 'ts')\n * \n * @example\n * ```typescript\n * // Pattern: \"{app}.config.{ext}\" with app=\"myapp\" and ext=\"yaml\"\n * // Results in: \"myapp.config.yaml\"\n * \n * const pattern: ConfigNamingPattern = {\n * pattern: '{app}.config.{ext}',\n * priority: 1,\n * hidden: false\n * };\n * ```\n */\nexport interface ConfigNamingPattern {\n /**\n * Pattern template with `{app}` and `{ext}` placeholders.\n * \n * Examples:\n * - `\"{app}.config.{ext}\"` → `\"protokoll.config.yaml\"`\n * - `\".{app}/config.{ext}\"` → `\".protokoll/config.yaml\"`\n * - `\".{app}rc.{ext}\"` → `\".protokollrc.json\"`\n * - `\".{app}rc\"` → `\".protokollrc\"` (no extension)\n */\n pattern: string;\n\n /**\n * Search priority (lower number = checked first).\n * When multiple config files exist, lower priority patterns take precedence.\n */\n priority: number;\n\n /**\n * Whether this pattern results in a hidden file or directory.\n * Hidden patterns start with a dot (e.g., `.myapp/`, `.myapprc`).\n */\n hidden: boolean;\n}\n\n/**\n * Options for configuring how configuration files are discovered.\n * \n * @example\n * ```typescript\n * const options: ConfigDiscoveryOptions = {\n * appName: 'myapp',\n * extensions: ['yaml', 'yml', 'json'],\n * searchHidden: true,\n * // Use custom patterns instead of defaults\n * patterns: [\n * { pattern: '{app}.config.{ext}', priority: 1, hidden: false }\n * ]\n * };\n * ```\n */\nexport interface ConfigDiscoveryOptions {\n /**\n * The application name used in pattern expansion.\n * This value replaces `{app}` placeholders in naming patterns.\n */\n appName: string;\n\n /**\n * Custom naming patterns to use for discovery.\n * If not provided, uses the standard patterns defined in STANDARD_PATTERNS.\n */\n patterns?: ConfigNamingPattern[];\n\n /**\n * File extensions to search for.\n * These replace the `{ext}` placeholder in patterns.\n * If not provided, defaults to supported format extensions.\n * \n * @example ['yaml', 'yml', 'json', 'js', 'ts']\n */\n extensions?: string[];\n\n /**\n * Whether to search for hidden files and directories.\n * When false, patterns with `hidden: true` are skipped.\n * \n * @default true\n */\n searchHidden?: boolean;\n\n /**\n * Whether to check for multiple config files and emit a warning.\n * When enabled, discovery continues after finding the first match\n * to detect and warn about additional config files that would be ignored.\n * \n * @default true\n */\n warnOnMultipleConfigs?: boolean;\n}\n\n/**\n * Result of discovering a configuration file.\n * Contains the file path and the pattern that matched.\n */\nexport interface DiscoveredConfig {\n /**\n * The resolved file path to the configuration file.\n * Can be a file path (e.g., 'app.config.yaml') or include\n * a directory (e.g., '.app/config.yaml').\n */\n path: string;\n\n /**\n * The absolute path to the configuration file.\n */\n absolutePath: string;\n\n /**\n * The pattern that matched this configuration file.\n */\n pattern: ConfigNamingPattern;\n}\n\n/**\n * Warning information when multiple config files are found.\n * This helps users identify and remove unused config files.\n */\nexport interface MultipleConfigWarning {\n /**\n * The configuration that will be used (highest priority).\n */\n used: DiscoveredConfig;\n\n /**\n * Configurations that were found but will be ignored.\n */\n ignored: DiscoveredConfig[];\n}\n\n/**\n * Full result of configuration discovery, including warnings.\n */\nexport interface DiscoveryResult {\n /**\n * The discovered configuration file, or null if none found.\n */\n config: DiscoveredConfig | null;\n\n /**\n * Warning about multiple config files, if any were found.\n */\n multipleConfigWarning?: MultipleConfigWarning;\n}\n\n// ============================================================================\n// Hierarchical Configuration Types\n// ============================================================================\n\n/**\n * Controls how hierarchical configuration lookup behaves.\n * \n * - `'enabled'` - Walk up the directory tree and merge configs (default behavior).\n * Configurations from parent directories are merged with child configurations,\n * with child values taking precedence.\n * \n * - `'disabled'` - Use only the config found in the starting directory.\n * No parent directory traversal occurs. Useful for isolated projects or\n * MCP configurations that should be self-contained.\n * \n * - `'root-only'` - Walk up to find the first config, but don't merge with others.\n * This mode finds the \"closest\" config file without merging parent configs.\n * Useful when you want automatic config discovery but not inheritance.\n * \n * - `'explicit'` - Only merge configs that are explicitly referenced.\n * The base config can specify which parent configs to extend via an\n * `extends` field. No automatic directory traversal.\n * \n * @example\n * ```typescript\n * // In a child config that wants to be isolated:\n * // protokoll.config.yaml\n * hierarchical:\n * mode: disabled\n * \n * // This config will NOT inherit from parent directories\n * ```\n */\nexport type HierarchicalMode = 'enabled' | 'disabled' | 'root-only' | 'explicit';\n\n/**\n * Files or directories that indicate a project root.\n * When encountered during directory traversal, hierarchical lookup stops.\n * \n * @example\n * ```typescript\n * const markers: RootMarker[] = [\n * { type: 'file', name: 'package.json' },\n * { type: 'directory', name: '.git' },\n * { type: 'file', name: 'pnpm-workspace.yaml' },\n * ];\n * ```\n */\nexport interface RootMarker {\n /** Type of the marker */\n type: 'file' | 'directory';\n /** Name of the file or directory that indicates a root */\n name: string;\n}\n\n/**\n * Default root markers used when none are specified.\n * These indicate common project root boundaries.\n */\nexport const DEFAULT_ROOT_MARKERS: RootMarker[] = [\n { type: 'file', name: 'package.json' },\n { type: 'directory', name: '.git' },\n { type: 'file', name: 'pnpm-workspace.yaml' },\n { type: 'file', name: 'lerna.json' },\n { type: 'file', name: 'nx.json' },\n { type: 'file', name: 'rush.json' },\n];\n\n/**\n * Configuration options for hierarchical config behavior.\n * Can be set in the configuration file or programmatically.\n * \n * @example\n * ```typescript\n * // Configuration file (protokoll.config.yaml):\n * hierarchical:\n * mode: enabled\n * maxDepth: 5\n * stopAt:\n * - node_modules\n * - vendor\n * rootMarkers:\n * - type: file\n * name: package.json\n * ```\n * \n * @example\n * ```typescript\n * // Programmatic configuration:\n * const options: HierarchicalOptions = {\n * mode: 'disabled', // No parent config merging\n * };\n * \n * // For MCP servers:\n * const mcpOptions: HierarchicalOptions = {\n * mode: 'root-only',\n * rootMarkers: [{ type: 'file', name: 'mcp.json' }],\n * };\n * ```\n */\nexport interface HierarchicalOptions {\n /**\n * The hierarchical lookup mode.\n * Controls whether and how parent directories are searched.\n * \n * @default 'enabled'\n */\n mode?: HierarchicalMode;\n\n /**\n * Maximum number of parent directories to traverse.\n * Prevents unbounded traversal in deep directory structures.\n * \n * @default 10\n */\n maxDepth?: number;\n\n /**\n * Directory names where traversal should stop.\n * When a directory with one of these names is encountered as a parent,\n * traversal stops even if no config was found.\n * \n * @example ['node_modules', 'vendor', '.cache']\n */\n stopAt?: string[];\n\n /**\n * Files or directories that indicate a project root.\n * When a directory contains one of these markers, it's treated as a root\n * and traversal stops after processing that directory.\n * \n * If not specified, uses DEFAULT_ROOT_MARKERS.\n * Set to empty array to disable root marker detection.\n */\n rootMarkers?: RootMarker[];\n\n /**\n * Whether to stop at the first root marker found.\n * When true, traversal stops immediately when a root marker is found.\n * When false, the directory with the root marker is included but no further.\n * \n * @default true\n */\n stopAtRoot?: boolean;\n}\n\n// ============================================================================\n// Directory Traversal Security Types\n// ============================================================================\n\n/**\n * Defines security boundaries for directory traversal.\n * Used to prevent configuration lookup from accessing sensitive directories.\n * \n * @example\n * ```typescript\n * const boundaries: TraversalBoundary = {\n * forbidden: ['/etc', '/usr', '/var'],\n * boundaries: [process.env.HOME ?? '/home'],\n * maxAbsoluteDepth: 20,\n * maxRelativeDepth: 10,\n * };\n * ```\n */\nexport interface TraversalBoundary {\n /**\n * Directories that are never allowed to be accessed.\n * Traversal is blocked if the path is at or within these directories.\n * Paths can include environment variable placeholders like `$HOME`.\n * \n * @example ['/etc', '/usr', '/var', '/sys', '/proc', '$HOME/.ssh']\n */\n forbidden: string[];\n\n /**\n * Soft boundary directories - traversal stops at these unless explicitly allowed.\n * These represent natural project boundaries.\n * Paths can include environment variable placeholders like `$HOME`.\n * \n * @example ['$HOME', '/tmp', '/private/tmp']\n */\n boundaries: string[];\n\n /**\n * Maximum absolute depth from the filesystem root.\n * Prevents extremely deep traversal regardless of starting point.\n * Depth is counted as the number of path segments from root.\n * \n * @example 20 means paths like /a/b/c/.../t (20 levels deep) are allowed\n * @default 20\n */\n maxAbsoluteDepth: number;\n\n /**\n * Maximum relative depth from the starting directory.\n * Limits how far up the directory tree traversal can go.\n * \n * @example 10 means traversal can go up 10 directories from the start\n * @default 10\n */\n maxRelativeDepth: number;\n}\n\n/**\n * Result of a traversal boundary check.\n */\nexport interface TraversalCheckResult {\n /** Whether the path is allowed */\n allowed: boolean;\n \n /** Reason for blocking (if not allowed) */\n reason?: string;\n \n /** The boundary that was violated (if any) */\n violatedBoundary?: string;\n}\n\n/**\n * Options for configuring traversal security behavior.\n */\nexport interface TraversalSecurityOptions {\n /**\n * Custom traversal boundaries to use instead of defaults.\n */\n boundaries?: Partial<TraversalBoundary>;\n\n /**\n * Allow traversal beyond safe boundaries.\n * \n * **SECURITY WARNING**: Setting this to true bypasses security checks\n * and allows traversal into sensitive directories. Only use this in\n * trusted scenarios where you control all configuration files.\n * \n * @default false\n */\n allowUnsafeTraversal?: boolean;\n\n /**\n * Whether to log warnings when boundaries are overridden.\n * \n * @default true\n */\n warnOnOverride?: boolean;\n}\n"],"names":["ConfigFormat","ConfigSchema","z","object","configDirectory","string","discoveredConfigDirs","array","resolvedConfigDirs","DEFAULT_ROOT_MARKERS","type","name"],"mappings":";;AAuBA;;;;;;;IAQO,IAAKA,YAAAA,iBAAAA,SAAAA,YAAAA,EAAAA;;;;;AAAAA,IAAAA,OAAAA,YAAAA;AAKX,CAAA,CAAA,EAAA;AA8MD;;;AAGC,IACM,MAAMC,YAAAA,GAAeC,CAAAA,CAAEC,MAAM,CAAC;qDAEjCC,eAAAA,EAAiBF,CAAAA,CAAEG,MAAM,EAAA;AACzB,wFACAC,oBAAAA,EAAsBJ,CAAAA,CAAEK,KAAK,CAACL,EAAEG,MAAM,EAAA,CAAA;AACtC,sFACAG,kBAAAA,EAAoBN,CAAAA,CAAEK,KAAK,CAACL,EAAEG,MAAM,EAAA;AACxC,CAAA;AA6NA;;;UAIaI,oBAAAA,GAAqC;AAC9C,IAAA;QAAEC,IAAAA,EAAM,MAAA;QAAQC,IAAAA,EAAM;AAAe,KAAA;AACrC,IAAA;QAAED,IAAAA,EAAM,WAAA;QAAaC,IAAAA,EAAM;AAAO,KAAA;AAClC,IAAA;QAAED,IAAAA,EAAM,MAAA;QAAQC,IAAAA,EAAM;AAAsB,KAAA;AAC5C,IAAA;QAAED,IAAAA,EAAM,MAAA;QAAQC,IAAAA,EAAM;AAAa,KAAA;AACnC,IAAA;QAAED,IAAAA,EAAM,MAAA;QAAQC,IAAAA,EAAM;AAAU,KAAA;AAChC,IAAA;QAAED,IAAAA,EAAM,MAAA;QAAQC,IAAAA,EAAM;AAAY;;;;;"}
@@ -0,0 +1,136 @@
1
+ import { Logger, FieldOverlapOptions } from '../types';
2
+ /**
3
+ * Represents a discovered configuration directory with its path and precedence level.
4
+ */
5
+ export interface DiscoveredConfigDir {
6
+ /** Absolute path to the configuration directory */
7
+ path: string;
8
+ /** Distance from the starting directory (0 = closest/highest precedence) */
9
+ level: number;
10
+ }
11
+ /**
12
+ * Options for hierarchical configuration discovery.
13
+ */
14
+ export interface HierarchicalDiscoveryOptions {
15
+ /** Name of the configuration directory to look for (e.g., '.kodrdriv') */
16
+ configDirName: string;
17
+ /** Name of the configuration file within each directory */
18
+ configFileName: string;
19
+ /** Maximum number of parent directories to traverse (default: 10) */
20
+ maxLevels?: number;
21
+ /** Starting directory for discovery (default: process.cwd()) */
22
+ startingDir?: string;
23
+ /** File encoding for reading configuration files */
24
+ encoding?: string;
25
+ /** Logger for debugging */
26
+ logger?: Logger;
27
+ /** Array of field names that contain paths to be resolved */
28
+ pathFields?: string[];
29
+ /** Array of field names whose array elements should all be resolved as paths */
30
+ resolvePathArray?: string[];
31
+ /** Configuration for how array fields should be merged in hierarchical mode */
32
+ fieldOverlaps?: FieldOverlapOptions;
33
+ }
34
+ /**
35
+ * Result of loading configurations from multiple directories.
36
+ */
37
+ export interface HierarchicalConfigResult {
38
+ /** Merged configuration object with proper precedence */
39
+ config: object;
40
+ /** Array of directories where configuration was found */
41
+ discoveredDirs: DiscoveredConfigDir[];
42
+ /** Array of directories that actually contained valid configuration files */
43
+ resolvedConfigDirs: DiscoveredConfigDir[];
44
+ /** Array of any errors encountered during loading (non-fatal) */
45
+ errors: string[];
46
+ }
47
+ /**
48
+ * Discovers configuration directories by traversing up the directory tree.
49
+ *
50
+ * Starting from the specified directory (or current working directory),
51
+ * this function searches for directories with the given name, continuing
52
+ * up the directory tree until it reaches the filesystem root or the
53
+ * maximum number of levels.
54
+ *
55
+ * @param options Configuration options for discovery
56
+ * @returns Promise resolving to array of discovered configuration directories
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const dirs = await discoverConfigDirectories({
61
+ * configDirName: '.kodrdriv',
62
+ * configFileName: 'config.yaml',
63
+ * maxLevels: 5
64
+ * });
65
+ * // Returns: [
66
+ * // { path: '/project/.kodrdriv', level: 0 },
67
+ * // { path: '/project/parent/.kodrdriv', level: 1 }
68
+ * // ]
69
+ * ```
70
+ */
71
+ export declare function discoverConfigDirectories(options: HierarchicalDiscoveryOptions): Promise<DiscoveredConfigDir[]>;
72
+ /**
73
+ * Loads and parses a configuration file from a directory.
74
+ *
75
+ * @param configDir Path to the configuration directory
76
+ * @param configFileName Name of the configuration file
77
+ * @param encoding File encoding
78
+ * @param logger Optional logger
79
+ * @param pathFields Optional array of field names that contain paths to be resolved
80
+ * @param resolvePathArray Optional array of field names whose array elements should all be resolved as paths
81
+ * @returns Promise resolving to parsed configuration object or null if not found
82
+ */
83
+ export declare function loadConfigFromDirectory(configDir: string, configFileName: string, encoding?: string, logger?: Logger, pathFields?: string[], resolvePathArray?: string[]): Promise<object | null>;
84
+ /**
85
+ * Deep merges multiple configuration objects with proper precedence and configurable array overlap behavior.
86
+ *
87
+ * Objects are merged from lowest precedence to highest precedence,
88
+ * meaning that properties in later objects override properties in earlier objects.
89
+ * Arrays can be merged using different strategies based on the fieldOverlaps configuration.
90
+ *
91
+ * @param configs Array of configuration objects, ordered from lowest to highest precedence
92
+ * @param fieldOverlaps Configuration for how array fields should be merged (optional)
93
+ * @returns Merged configuration object
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const merged = deepMergeConfigs([
98
+ * { api: { timeout: 5000 }, features: ['auth'] }, // Lower precedence
99
+ * { api: { retries: 3 }, features: ['analytics'] }, // Higher precedence
100
+ * ], {
101
+ * 'features': 'append' // Results in features: ['auth', 'analytics']
102
+ * });
103
+ * ```
104
+ */
105
+ export declare function deepMergeConfigs(configs: object[], fieldOverlaps?: FieldOverlapOptions): object;
106
+ /**
107
+ * Loads configurations from multiple directories and merges them with proper precedence.
108
+ *
109
+ * This is the main function for hierarchical configuration loading. It:
110
+ * 1. Discovers configuration directories up the directory tree
111
+ * 2. Loads configuration files from each discovered directory
112
+ * 3. Merges them with proper precedence (closer directories win)
113
+ * 4. Returns the merged configuration with metadata
114
+ *
115
+ * @param options Configuration options for hierarchical loading
116
+ * @returns Promise resolving to hierarchical configuration result
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const result = await loadHierarchicalConfig({
121
+ * configDirName: '.kodrdriv',
122
+ * configFileName: 'config.yaml',
123
+ * startingDir: '/project/subdir',
124
+ * maxLevels: 5,
125
+ * fieldOverlaps: {
126
+ * 'features': 'append',
127
+ * 'excludePatterns': 'prepend'
128
+ * }
129
+ * });
130
+ *
131
+ * // result.config contains merged configuration with custom array merging
132
+ * // result.discoveredDirs shows where configs were found
133
+ * // result.errors contains any non-fatal errors
134
+ * ```
135
+ */
136
+ export declare function loadHierarchicalConfig(options: HierarchicalDiscoveryOptions): Promise<HierarchicalConfigResult>;
@@ -0,0 +1,436 @@
1
+ import * as path from 'node:path';
2
+ import * as yaml from 'js-yaml';
3
+ import { create } from './storage.js';
4
+
5
+ /**
6
+ * Resolves relative paths in configuration values relative to the configuration file's directory.
7
+ */ function resolveConfigPaths(config, configDir, pathFields = [], resolvePathArray = []) {
8
+ if (!config || typeof config !== 'object' || pathFields.length === 0) {
9
+ return config;
10
+ }
11
+ const resolvedConfig = {
12
+ ...config
13
+ };
14
+ for (const fieldPath of pathFields){
15
+ const value = getNestedValue(resolvedConfig, fieldPath);
16
+ if (value !== undefined) {
17
+ const shouldResolveArrayElements = resolvePathArray.includes(fieldPath);
18
+ const resolvedValue = resolvePathValue(value, configDir, shouldResolveArrayElements);
19
+ setNestedValue(resolvedConfig, fieldPath, resolvedValue);
20
+ }
21
+ }
22
+ return resolvedConfig;
23
+ }
24
+ /**
25
+ * Gets a nested value from an object using dot notation.
26
+ */ function getNestedValue(obj, path) {
27
+ return path.split('.').reduce((current, key)=>current === null || current === void 0 ? void 0 : current[key], obj);
28
+ }
29
+ /**
30
+ * Sets a nested value in an object using dot notation.
31
+ */ function isUnsafeKey(key) {
32
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
33
+ }
34
+ function setNestedValue(obj, path, value) {
35
+ const keys = path.split('.');
36
+ const lastKey = keys.pop();
37
+ // Prevent prototype pollution via special property names
38
+ if (isUnsafeKey(lastKey) || keys.some(isUnsafeKey)) {
39
+ return;
40
+ }
41
+ const target = keys.reduce((current, key)=>{
42
+ // Skip if this is an unsafe key (already checked above, but defensive)
43
+ if (isUnsafeKey(key)) {
44
+ return current;
45
+ }
46
+ if (!(key in current)) {
47
+ current[key] = {};
48
+ }
49
+ return current[key];
50
+ }, obj);
51
+ target[lastKey] = value;
52
+ }
53
+ /**
54
+ * Resolves a path value (string or array of strings) relative to the config directory.
55
+ */ function resolvePathValue(value, configDir, resolveArrayElements) {
56
+ if (typeof value === 'string') {
57
+ return resolveSinglePath(value, configDir);
58
+ }
59
+ if (Array.isArray(value) && resolveArrayElements) {
60
+ return value.map((item)=>typeof item === 'string' ? resolveSinglePath(item, configDir) : item);
61
+ }
62
+ return value;
63
+ }
64
+ /**
65
+ * Resolves a single path string relative to the config directory if it's a relative path.
66
+ */ function resolveSinglePath(pathStr, configDir) {
67
+ if (!pathStr || path.isAbsolute(pathStr)) {
68
+ return pathStr;
69
+ }
70
+ return path.resolve(configDir, pathStr);
71
+ }
72
+ /**
73
+ * Discovers configuration directories by traversing up the directory tree.
74
+ *
75
+ * Starting from the specified directory (or current working directory),
76
+ * this function searches for directories with the given name, continuing
77
+ * up the directory tree until it reaches the filesystem root or the
78
+ * maximum number of levels.
79
+ *
80
+ * @param options Configuration options for discovery
81
+ * @returns Promise resolving to array of discovered configuration directories
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const dirs = await discoverConfigDirectories({
86
+ * configDirName: '.kodrdriv',
87
+ * configFileName: 'config.yaml',
88
+ * maxLevels: 5
89
+ * });
90
+ * // Returns: [
91
+ * // { path: '/project/.kodrdriv', level: 0 },
92
+ * // { path: '/project/parent/.kodrdriv', level: 1 }
93
+ * // ]
94
+ * ```
95
+ */ async function discoverConfigDirectories(options) {
96
+ const { configDirName, maxLevels = 10, startingDir = process.cwd(), logger } = options;
97
+ const storage = create({
98
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
99
+ });
100
+ const discoveredDirs = [];
101
+ let currentDir = path.resolve(startingDir);
102
+ let level = 0;
103
+ const visited = new Set(); // Prevent infinite loops with symlinks
104
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Starting hierarchical discovery from: ${currentDir}`);
105
+ while(level < maxLevels){
106
+ // Prevent infinite loops with symlinks
107
+ const realPath = path.resolve(currentDir);
108
+ if (visited.has(realPath)) {
109
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Already visited ${realPath}, stopping discovery`);
110
+ break;
111
+ }
112
+ visited.add(realPath);
113
+ const configDirPath = path.join(currentDir, configDirName);
114
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Checking for config directory: ${configDirPath}`);
115
+ try {
116
+ const exists = await storage.exists(configDirPath);
117
+ const isReadable = exists && await storage.isDirectoryReadable(configDirPath);
118
+ if (exists && isReadable) {
119
+ discoveredDirs.push({
120
+ path: configDirPath,
121
+ level
122
+ });
123
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Found config directory at level ${level}: ${configDirPath}`);
124
+ } else if (exists && !isReadable) {
125
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config directory exists but is not readable: ${configDirPath}`);
126
+ }
127
+ } catch (error) {
128
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error checking config directory ${configDirPath}: ${error.message}`);
129
+ }
130
+ // Move up one directory level
131
+ const parentDir = path.dirname(currentDir);
132
+ // Check if we've reached the root directory
133
+ if (parentDir === currentDir) {
134
+ logger === null || logger === void 0 ? void 0 : logger.debug('Reached filesystem root, stopping discovery');
135
+ break;
136
+ }
137
+ currentDir = parentDir;
138
+ level++;
139
+ }
140
+ logger === null || logger === void 0 ? void 0 : logger.verbose(`Discovery complete. Found ${discoveredDirs.length} config directories`);
141
+ return discoveredDirs;
142
+ }
143
+ /**
144
+ * Tries to find a config file with alternative extensions (.yaml or .yml).
145
+ *
146
+ * @param storage Storage instance to use for file operations
147
+ * @param configDir The directory containing the config file
148
+ * @param configFileName The base config file name (may have .yaml or .yml extension)
149
+ * @param logger Optional logger for debugging
150
+ * @returns Promise resolving to the found config file path or null if not found
151
+ */ async function findConfigFileWithExtension(storage, configDir, configFileName, logger) {
152
+ const configFilePath = path.join(configDir, configFileName);
153
+ // First try the exact filename as specified
154
+ const exists = await storage.exists(configFilePath);
155
+ if (exists) {
156
+ const isReadable = await storage.isFileReadable(configFilePath);
157
+ if (isReadable) {
158
+ return configFilePath;
159
+ }
160
+ }
161
+ // If the exact filename doesn't exist or isn't readable, try alternative extensions
162
+ // Only do this if the filename has a .yaml or .yml extension
163
+ const ext = path.extname(configFileName);
164
+ if (ext === '.yaml' || ext === '.yml') {
165
+ const baseName = path.basename(configFileName, ext);
166
+ const alternativeExt = ext === '.yaml' ? '.yml' : '.yaml';
167
+ const alternativePath = path.join(configDir, baseName + alternativeExt);
168
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file not found at ${configFilePath}, trying alternative: ${alternativePath}`);
169
+ const altExists = await storage.exists(alternativePath);
170
+ if (altExists) {
171
+ const altIsReadable = await storage.isFileReadable(alternativePath);
172
+ if (altIsReadable) {
173
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Found config file with alternative extension: ${alternativePath}`);
174
+ return alternativePath;
175
+ }
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+ /**
181
+ * Loads and parses a configuration file from a directory.
182
+ *
183
+ * @param configDir Path to the configuration directory
184
+ * @param configFileName Name of the configuration file
185
+ * @param encoding File encoding
186
+ * @param logger Optional logger
187
+ * @param pathFields Optional array of field names that contain paths to be resolved
188
+ * @param resolvePathArray Optional array of field names whose array elements should all be resolved as paths
189
+ * @returns Promise resolving to parsed configuration object or null if not found
190
+ */ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray) {
191
+ const storage = create({
192
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
193
+ });
194
+ try {
195
+ logger === null || logger === void 0 ? void 0 : logger.verbose(`Attempting to load config file: ${path.join(configDir, configFileName)}`);
196
+ // Try to find the config file with alternative extensions
197
+ const configFilePath = await findConfigFileWithExtension(storage, configDir, configFileName, logger);
198
+ if (!configFilePath) {
199
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file does not exist: ${path.join(configDir, configFileName)}`);
200
+ return null;
201
+ }
202
+ const yamlContent = await storage.readFile(configFilePath, encoding);
203
+ const parsedYaml = yaml.load(yamlContent);
204
+ if (parsedYaml !== null && typeof parsedYaml === 'object') {
205
+ let config = parsedYaml;
206
+ // Apply path resolution if configured
207
+ if (pathFields && pathFields.length > 0) {
208
+ config = resolveConfigPaths(config, configDir, pathFields, resolvePathArray || []);
209
+ }
210
+ logger === null || logger === void 0 ? void 0 : logger.verbose(`Successfully loaded config from: ${configFilePath}`);
211
+ return config;
212
+ } else {
213
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file contains invalid format: ${configFilePath}`);
214
+ return null;
215
+ }
216
+ } catch (error) {
217
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error loading config from ${path.join(configDir, configFileName)}: ${error.message}`);
218
+ return null;
219
+ }
220
+ }
221
+ /**
222
+ * Deep merges multiple configuration objects with proper precedence and configurable array overlap behavior.
223
+ *
224
+ * Objects are merged from lowest precedence to highest precedence,
225
+ * meaning that properties in later objects override properties in earlier objects.
226
+ * Arrays can be merged using different strategies based on the fieldOverlaps configuration.
227
+ *
228
+ * @param configs Array of configuration objects, ordered from lowest to highest precedence
229
+ * @param fieldOverlaps Configuration for how array fields should be merged (optional)
230
+ * @returns Merged configuration object
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const merged = deepMergeConfigs([
235
+ * { api: { timeout: 5000 }, features: ['auth'] }, // Lower precedence
236
+ * { api: { retries: 3 }, features: ['analytics'] }, // Higher precedence
237
+ * ], {
238
+ * 'features': 'append' // Results in features: ['auth', 'analytics']
239
+ * });
240
+ * ```
241
+ */ function deepMergeConfigs(configs, fieldOverlaps) {
242
+ if (configs.length === 0) {
243
+ return {};
244
+ }
245
+ if (configs.length === 1) {
246
+ return {
247
+ ...configs[0]
248
+ };
249
+ }
250
+ return configs.reduce((merged, current)=>{
251
+ return deepMergeTwo(merged, current, fieldOverlaps);
252
+ }, {});
253
+ }
254
+ /**
255
+ * Deep merges two objects with proper precedence and configurable array overlap behavior.
256
+ *
257
+ * @param target Target object (lower precedence)
258
+ * @param source Source object (higher precedence)
259
+ * @param fieldOverlaps Configuration for how array fields should be merged (optional)
260
+ * @param currentPath Current field path for nested merging (used internally)
261
+ * @returns Merged object
262
+ */ function deepMergeTwo(target, source, fieldOverlaps, currentPath = '') {
263
+ // Handle null/undefined
264
+ if (source == null) return target;
265
+ if (target == null) return source;
266
+ // Handle non-objects (primitives, arrays, functions, etc.)
267
+ if (typeof source !== 'object' || typeof target !== 'object') {
268
+ return source; // Source takes precedence
269
+ }
270
+ // Handle arrays with configurable overlap behavior
271
+ if (Array.isArray(source)) {
272
+ if (Array.isArray(target) && fieldOverlaps) {
273
+ const overlapMode = getOverlapModeForPath(currentPath, fieldOverlaps);
274
+ return mergeArrays(target, source, overlapMode);
275
+ } else {
276
+ // Default behavior: replace entirely
277
+ return [
278
+ ...source
279
+ ];
280
+ }
281
+ }
282
+ if (Array.isArray(target)) {
283
+ return source; // Source object replaces target array
284
+ }
285
+ // Deep merge objects
286
+ const result = {
287
+ ...target
288
+ };
289
+ for(const key in source){
290
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
291
+ const fieldPath = currentPath ? `${currentPath}.${key}` : key;
292
+ if (Object.prototype.hasOwnProperty.call(result, key) && typeof result[key] === 'object' && typeof source[key] === 'object' && !Array.isArray(source[key]) && !Array.isArray(result[key])) {
293
+ // Recursively merge nested objects
294
+ result[key] = deepMergeTwo(result[key], source[key], fieldOverlaps, fieldPath);
295
+ } else {
296
+ // Handle arrays and primitives with overlap consideration
297
+ if (Array.isArray(source[key]) && Array.isArray(result[key]) && fieldOverlaps) {
298
+ const overlapMode = getOverlapModeForPath(fieldPath, fieldOverlaps);
299
+ result[key] = mergeArrays(result[key], source[key], overlapMode);
300
+ } else {
301
+ // Replace with source value (higher precedence)
302
+ result[key] = source[key];
303
+ }
304
+ }
305
+ }
306
+ }
307
+ return result;
308
+ }
309
+ /**
310
+ * Determines the overlap mode for a given field path.
311
+ *
312
+ * @param fieldPath The current field path (dot notation)
313
+ * @param fieldOverlaps Configuration mapping field paths to overlap modes
314
+ * @returns The overlap mode to use for this field path
315
+ */ function getOverlapModeForPath(fieldPath, fieldOverlaps) {
316
+ // Check for exact match first
317
+ if (fieldPath in fieldOverlaps) {
318
+ return fieldOverlaps[fieldPath];
319
+ }
320
+ // Check for any parent path matches (for nested configurations)
321
+ const pathParts = fieldPath.split('.');
322
+ for(let i = pathParts.length - 1; i > 0; i--){
323
+ const parentPath = pathParts.slice(0, i).join('.');
324
+ if (parentPath in fieldOverlaps) {
325
+ return fieldOverlaps[parentPath];
326
+ }
327
+ }
328
+ // Default to override if no specific configuration found
329
+ return 'override';
330
+ }
331
+ /**
332
+ * Merges two arrays based on the specified overlap mode.
333
+ *
334
+ * @param targetArray The lower precedence array
335
+ * @param sourceArray The higher precedence array
336
+ * @param mode The overlap mode to use
337
+ * @returns The merged array
338
+ */ function mergeArrays(targetArray, sourceArray, mode) {
339
+ switch(mode){
340
+ case 'append':
341
+ return [
342
+ ...targetArray,
343
+ ...sourceArray
344
+ ];
345
+ case 'prepend':
346
+ return [
347
+ ...sourceArray,
348
+ ...targetArray
349
+ ];
350
+ case 'override':
351
+ default:
352
+ return [
353
+ ...sourceArray
354
+ ];
355
+ }
356
+ }
357
+ /**
358
+ * Loads configurations from multiple directories and merges them with proper precedence.
359
+ *
360
+ * This is the main function for hierarchical configuration loading. It:
361
+ * 1. Discovers configuration directories up the directory tree
362
+ * 2. Loads configuration files from each discovered directory
363
+ * 3. Merges them with proper precedence (closer directories win)
364
+ * 4. Returns the merged configuration with metadata
365
+ *
366
+ * @param options Configuration options for hierarchical loading
367
+ * @returns Promise resolving to hierarchical configuration result
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * const result = await loadHierarchicalConfig({
372
+ * configDirName: '.kodrdriv',
373
+ * configFileName: 'config.yaml',
374
+ * startingDir: '/project/subdir',
375
+ * maxLevels: 5,
376
+ * fieldOverlaps: {
377
+ * 'features': 'append',
378
+ * 'excludePatterns': 'prepend'
379
+ * }
380
+ * });
381
+ *
382
+ * // result.config contains merged configuration with custom array merging
383
+ * // result.discoveredDirs shows where configs were found
384
+ * // result.errors contains any non-fatal errors
385
+ * ```
386
+ */ async function loadHierarchicalConfig(options) {
387
+ const { configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray, fieldOverlaps } = options;
388
+ logger === null || logger === void 0 ? void 0 : logger.verbose('Starting hierarchical configuration loading');
389
+ // Discover all configuration directories
390
+ const discoveredDirs = await discoverConfigDirectories(options);
391
+ if (discoveredDirs.length === 0) {
392
+ logger === null || logger === void 0 ? void 0 : logger.verbose('No configuration directories found');
393
+ return {
394
+ config: {},
395
+ discoveredDirs: [],
396
+ resolvedConfigDirs: [],
397
+ errors: []
398
+ };
399
+ }
400
+ // Load configurations from each directory
401
+ const configs = [];
402
+ const resolvedConfigDirs = [];
403
+ const errors = [];
404
+ // Sort by level (highest level first = lowest precedence first)
405
+ const sortedDirs = [
406
+ ...discoveredDirs
407
+ ].sort((a, b)=>b.level - a.level);
408
+ for (const dir of sortedDirs){
409
+ try {
410
+ const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger, pathFields, resolvePathArray);
411
+ if (config !== null) {
412
+ configs.push(config);
413
+ resolvedConfigDirs.push(dir);
414
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Loaded config from level ${dir.level}: ${dir.path}`);
415
+ } else {
416
+ logger === null || logger === void 0 ? void 0 : logger.debug(`No valid config found at level ${dir.level}: ${dir.path}`);
417
+ }
418
+ } catch (error) {
419
+ const errorMsg = `Failed to load config from ${dir.path}: ${error.message}`;
420
+ errors.push(errorMsg);
421
+ logger === null || logger === void 0 ? void 0 : logger.debug(errorMsg);
422
+ }
423
+ }
424
+ // Merge all configurations with proper precedence and configurable array overlap
425
+ const mergedConfig = deepMergeConfigs(configs, fieldOverlaps);
426
+ logger === null || logger === void 0 ? void 0 : logger.verbose(`Hierarchical loading complete. Merged ${configs.length} configurations`);
427
+ return {
428
+ config: mergedConfig,
429
+ discoveredDirs,
430
+ resolvedConfigDirs,
431
+ errors
432
+ };
433
+ }
434
+
435
+ export { deepMergeConfigs, discoverConfigDirectories, loadConfigFromDirectory, loadHierarchicalConfig };
436
+ //# sourceMappingURL=hierarchical.js.map