@theunwalked/cardigantime 0.0.3 → 0.0.6

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.
@@ -0,0 +1,120 @@
1
+ import { Logger } 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
+ }
28
+ /**
29
+ * Result of loading configurations from multiple directories.
30
+ */
31
+ export interface HierarchicalConfigResult {
32
+ /** Merged configuration object with proper precedence */
33
+ config: object;
34
+ /** Array of directories where configuration was found */
35
+ discoveredDirs: DiscoveredConfigDir[];
36
+ /** Array of any errors encountered during loading (non-fatal) */
37
+ errors: string[];
38
+ }
39
+ /**
40
+ * Discovers configuration directories by traversing up the directory tree.
41
+ *
42
+ * Starting from the specified directory (or current working directory),
43
+ * this function searches for directories with the given name, continuing
44
+ * up the directory tree until it reaches the filesystem root or the
45
+ * maximum number of levels.
46
+ *
47
+ * @param options Configuration options for discovery
48
+ * @returns Promise resolving to array of discovered configuration directories
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const dirs = await discoverConfigDirectories({
53
+ * configDirName: '.kodrdriv',
54
+ * configFileName: 'config.yaml',
55
+ * maxLevels: 5
56
+ * });
57
+ * // Returns: [
58
+ * // { path: '/project/.kodrdriv', level: 0 },
59
+ * // { path: '/project/parent/.kodrdriv', level: 1 }
60
+ * // ]
61
+ * ```
62
+ */
63
+ export declare function discoverConfigDirectories(options: HierarchicalDiscoveryOptions): Promise<DiscoveredConfigDir[]>;
64
+ /**
65
+ * Loads and parses a configuration file from a directory.
66
+ *
67
+ * @param configDir Path to the configuration directory
68
+ * @param configFileName Name of the configuration file
69
+ * @param encoding File encoding
70
+ * @param logger Optional logger
71
+ * @returns Promise resolving to parsed configuration object or null if not found
72
+ */
73
+ export declare function loadConfigFromDirectory(configDir: string, configFileName: string, encoding?: string, logger?: Logger): Promise<object | null>;
74
+ /**
75
+ * Deep merges multiple configuration objects with proper precedence.
76
+ *
77
+ * Objects are merged from lowest precedence to highest precedence,
78
+ * meaning that properties in later objects override properties in earlier objects.
79
+ * Arrays are replaced entirely (not merged).
80
+ *
81
+ * @param configs Array of configuration objects, ordered from lowest to highest precedence
82
+ * @returns Merged configuration object
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const merged = deepMergeConfigs([
87
+ * { api: { timeout: 5000 }, debug: true }, // Lower precedence
88
+ * { api: { retries: 3 }, features: ['auth'] }, // Higher precedence
89
+ * ]);
90
+ * // Result: { api: { timeout: 5000, retries: 3 }, debug: true, features: ['auth'] }
91
+ * ```
92
+ */
93
+ export declare function deepMergeConfigs(configs: object[]): object;
94
+ /**
95
+ * Loads configurations from multiple directories and merges them with proper precedence.
96
+ *
97
+ * This is the main function for hierarchical configuration loading. It:
98
+ * 1. Discovers configuration directories up the directory tree
99
+ * 2. Loads configuration files from each discovered directory
100
+ * 3. Merges them with proper precedence (closer directories win)
101
+ * 4. Returns the merged configuration with metadata
102
+ *
103
+ * @param options Configuration options for hierarchical loading
104
+ * @returns Promise resolving to hierarchical configuration result
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const result = await loadHierarchicalConfig({
109
+ * configDirName: '.kodrdriv',
110
+ * configFileName: 'config.yaml',
111
+ * startingDir: '/project/subdir',
112
+ * maxLevels: 5
113
+ * });
114
+ *
115
+ * // result.config contains merged configuration
116
+ * // result.discoveredDirs shows where configs were found
117
+ * // result.errors contains any non-fatal errors
118
+ * ```
119
+ */
120
+ export declare function loadHierarchicalConfig(options: HierarchicalDiscoveryOptions): Promise<HierarchicalConfigResult>;
@@ -0,0 +1,257 @@
1
+ import path__default from 'path';
2
+ import * as yaml from 'js-yaml';
3
+ import { create } from './storage.js';
4
+
5
+ /**
6
+ * Discovers configuration directories by traversing up the directory tree.
7
+ *
8
+ * Starting from the specified directory (or current working directory),
9
+ * this function searches for directories with the given name, continuing
10
+ * up the directory tree until it reaches the filesystem root or the
11
+ * maximum number of levels.
12
+ *
13
+ * @param options Configuration options for discovery
14
+ * @returns Promise resolving to array of discovered configuration directories
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const dirs = await discoverConfigDirectories({
19
+ * configDirName: '.kodrdriv',
20
+ * configFileName: 'config.yaml',
21
+ * maxLevels: 5
22
+ * });
23
+ * // Returns: [
24
+ * // { path: '/project/.kodrdriv', level: 0 },
25
+ * // { path: '/project/parent/.kodrdriv', level: 1 }
26
+ * // ]
27
+ * ```
28
+ */ async function discoverConfigDirectories(options) {
29
+ const { configDirName, maxLevels = 10, startingDir = process.cwd(), logger } = options;
30
+ const storage = create({
31
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
32
+ });
33
+ const discoveredDirs = [];
34
+ let currentDir = path__default.resolve(startingDir);
35
+ let level = 0;
36
+ const visited = new Set(); // Prevent infinite loops with symlinks
37
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Starting hierarchical discovery from: ${currentDir}`);
38
+ while(level < maxLevels){
39
+ // Prevent infinite loops with symlinks
40
+ const realPath = path__default.resolve(currentDir);
41
+ if (visited.has(realPath)) {
42
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Already visited ${realPath}, stopping discovery`);
43
+ break;
44
+ }
45
+ visited.add(realPath);
46
+ const configDirPath = path__default.join(currentDir, configDirName);
47
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Checking for config directory: ${configDirPath}`);
48
+ try {
49
+ const exists = await storage.exists(configDirPath);
50
+ const isReadable = exists && await storage.isDirectoryReadable(configDirPath);
51
+ if (exists && isReadable) {
52
+ discoveredDirs.push({
53
+ path: configDirPath,
54
+ level
55
+ });
56
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Found config directory at level ${level}: ${configDirPath}`);
57
+ } else if (exists && !isReadable) {
58
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config directory exists but is not readable: ${configDirPath}`);
59
+ }
60
+ } catch (error) {
61
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error checking config directory ${configDirPath}: ${error.message}`);
62
+ }
63
+ // Move up one directory level
64
+ const parentDir = path__default.dirname(currentDir);
65
+ // Check if we've reached the root directory
66
+ if (parentDir === currentDir) {
67
+ logger === null || logger === void 0 ? void 0 : logger.debug('Reached filesystem root, stopping discovery');
68
+ break;
69
+ }
70
+ currentDir = parentDir;
71
+ level++;
72
+ }
73
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Discovery complete. Found ${discoveredDirs.length} config directories`);
74
+ return discoveredDirs;
75
+ }
76
+ /**
77
+ * Loads and parses a configuration file from a directory.
78
+ *
79
+ * @param configDir Path to the configuration directory
80
+ * @param configFileName Name of the configuration file
81
+ * @param encoding File encoding
82
+ * @param logger Optional logger
83
+ * @returns Promise resolving to parsed configuration object or null if not found
84
+ */ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger) {
85
+ const storage = create({
86
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
87
+ });
88
+ const configFilePath = path__default.join(configDir, configFileName);
89
+ try {
90
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Attempting to load config file: ${configFilePath}`);
91
+ const exists = await storage.exists(configFilePath);
92
+ if (!exists) {
93
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file does not exist: ${configFilePath}`);
94
+ return null;
95
+ }
96
+ const isReadable = await storage.isFileReadable(configFilePath);
97
+ if (!isReadable) {
98
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file exists but is not readable: ${configFilePath}`);
99
+ return null;
100
+ }
101
+ const yamlContent = await storage.readFile(configFilePath, encoding);
102
+ const parsedYaml = yaml.load(yamlContent);
103
+ if (parsedYaml !== null && typeof parsedYaml === 'object') {
104
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Successfully loaded config from: ${configFilePath}`);
105
+ return parsedYaml;
106
+ } else {
107
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file contains invalid format: ${configFilePath}`);
108
+ return null;
109
+ }
110
+ } catch (error) {
111
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error loading config from ${configFilePath}: ${error.message}`);
112
+ return null;
113
+ }
114
+ }
115
+ /**
116
+ * Deep merges multiple configuration objects with proper precedence.
117
+ *
118
+ * Objects are merged from lowest precedence to highest precedence,
119
+ * meaning that properties in later objects override properties in earlier objects.
120
+ * Arrays are replaced entirely (not merged).
121
+ *
122
+ * @param configs Array of configuration objects, ordered from lowest to highest precedence
123
+ * @returns Merged configuration object
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const merged = deepMergeConfigs([
128
+ * { api: { timeout: 5000 }, debug: true }, // Lower precedence
129
+ * { api: { retries: 3 }, features: ['auth'] }, // Higher precedence
130
+ * ]);
131
+ * // Result: { api: { timeout: 5000, retries: 3 }, debug: true, features: ['auth'] }
132
+ * ```
133
+ */ function deepMergeConfigs(configs) {
134
+ if (configs.length === 0) {
135
+ return {};
136
+ }
137
+ if (configs.length === 1) {
138
+ return {
139
+ ...configs[0]
140
+ };
141
+ }
142
+ return configs.reduce((merged, current)=>{
143
+ return deepMergeTwo(merged, current);
144
+ }, {});
145
+ }
146
+ /**
147
+ * Deep merges two objects with proper precedence.
148
+ *
149
+ * @param target Target object (lower precedence)
150
+ * @param source Source object (higher precedence)
151
+ * @returns Merged object
152
+ */ function deepMergeTwo(target, source) {
153
+ // Handle null/undefined
154
+ if (source == null) return target;
155
+ if (target == null) return source;
156
+ // Handle non-objects (primitives, arrays, functions, etc.)
157
+ if (typeof source !== 'object' || typeof target !== 'object') {
158
+ return source; // Source takes precedence
159
+ }
160
+ // Handle arrays - replace entirely, don't merge
161
+ if (Array.isArray(source)) {
162
+ return [
163
+ ...source
164
+ ];
165
+ }
166
+ if (Array.isArray(target)) {
167
+ return source; // Source object replaces target array
168
+ }
169
+ // Deep merge objects
170
+ const result = {
171
+ ...target
172
+ };
173
+ for(const key in source){
174
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
175
+ if (Object.prototype.hasOwnProperty.call(result, key) && typeof result[key] === 'object' && typeof source[key] === 'object' && !Array.isArray(source[key]) && !Array.isArray(result[key])) {
176
+ // Recursively merge nested objects
177
+ result[key] = deepMergeTwo(result[key], source[key]);
178
+ } else {
179
+ // Replace with source value (higher precedence)
180
+ result[key] = source[key];
181
+ }
182
+ }
183
+ }
184
+ return result;
185
+ }
186
+ /**
187
+ * Loads configurations from multiple directories and merges them with proper precedence.
188
+ *
189
+ * This is the main function for hierarchical configuration loading. It:
190
+ * 1. Discovers configuration directories up the directory tree
191
+ * 2. Loads configuration files from each discovered directory
192
+ * 3. Merges them with proper precedence (closer directories win)
193
+ * 4. Returns the merged configuration with metadata
194
+ *
195
+ * @param options Configuration options for hierarchical loading
196
+ * @returns Promise resolving to hierarchical configuration result
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * const result = await loadHierarchicalConfig({
201
+ * configDirName: '.kodrdriv',
202
+ * configFileName: 'config.yaml',
203
+ * startingDir: '/project/subdir',
204
+ * maxLevels: 5
205
+ * });
206
+ *
207
+ * // result.config contains merged configuration
208
+ * // result.discoveredDirs shows where configs were found
209
+ * // result.errors contains any non-fatal errors
210
+ * ```
211
+ */ async function loadHierarchicalConfig(options) {
212
+ const { configFileName, encoding = 'utf8', logger } = options;
213
+ logger === null || logger === void 0 ? void 0 : logger.debug('Starting hierarchical configuration loading');
214
+ // Discover all configuration directories
215
+ const discoveredDirs = await discoverConfigDirectories(options);
216
+ if (discoveredDirs.length === 0) {
217
+ logger === null || logger === void 0 ? void 0 : logger.debug('No configuration directories found');
218
+ return {
219
+ config: {},
220
+ discoveredDirs: [],
221
+ errors: []
222
+ };
223
+ }
224
+ // Load configurations from each directory
225
+ const configs = [];
226
+ const errors = [];
227
+ // Sort by level (highest level first = lowest precedence first)
228
+ const sortedDirs = [
229
+ ...discoveredDirs
230
+ ].sort((a, b)=>b.level - a.level);
231
+ for (const dir of sortedDirs){
232
+ try {
233
+ const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger);
234
+ if (config !== null) {
235
+ configs.push(config);
236
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Loaded config from level ${dir.level}: ${dir.path}`);
237
+ } else {
238
+ logger === null || logger === void 0 ? void 0 : logger.debug(`No valid config found at level ${dir.level}: ${dir.path}`);
239
+ }
240
+ } catch (error) {
241
+ const errorMsg = `Failed to load config from ${dir.path}: ${error.message}`;
242
+ errors.push(errorMsg);
243
+ logger === null || logger === void 0 ? void 0 : logger.debug(errorMsg);
244
+ }
245
+ }
246
+ // Merge all configurations with proper precedence
247
+ const mergedConfig = deepMergeConfigs(configs);
248
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Hierarchical loading complete. Merged ${configs.length} configurations`);
249
+ return {
250
+ config: mergedConfig,
251
+ discoveredDirs,
252
+ errors
253
+ };
254
+ }
255
+
256
+ export { deepMergeConfigs, discoverConfigDirectories, loadConfigFromDirectory, loadHierarchicalConfig };
257
+ //# sourceMappingURL=hierarchical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical.js","sources":["../../src/util/hierarchical.ts"],"sourcesContent":["import path from 'path';\nimport * as yaml from 'js-yaml';\nimport { create as createStorage } from './storage';\nimport { Logger } from '../types';\n\n/**\n * Represents a discovered configuration directory with its path and precedence level.\n */\nexport interface DiscoveredConfigDir {\n /** Absolute path to the configuration directory */\n path: string;\n /** Distance from the starting directory (0 = closest/highest precedence) */\n level: number;\n}\n\n/**\n * Options for hierarchical configuration discovery.\n */\nexport interface HierarchicalDiscoveryOptions {\n /** Name of the configuration directory to look for (e.g., '.kodrdriv') */\n configDirName: string;\n /** Name of the configuration file within each directory */\n configFileName: string;\n /** Maximum number of parent directories to traverse (default: 10) */\n maxLevels?: number;\n /** Starting directory for discovery (default: process.cwd()) */\n startingDir?: string;\n /** File encoding for reading configuration files */\n encoding?: string;\n /** Logger for debugging */\n logger?: Logger;\n}\n\n/**\n * Result of loading configurations from multiple directories.\n */\nexport interface HierarchicalConfigResult {\n /** Merged configuration object with proper precedence */\n config: object;\n /** Array of directories where configuration was found */\n discoveredDirs: DiscoveredConfigDir[];\n /** Array of any errors encountered during loading (non-fatal) */\n errors: string[];\n}\n\n/**\n * Discovers configuration directories by traversing up the directory tree.\n * \n * Starting from the specified directory (or current working directory),\n * this function searches for directories with the given name, continuing\n * up the directory tree until it reaches the filesystem root or the\n * maximum number of levels.\n * \n * @param options Configuration options for discovery\n * @returns Promise resolving to array of discovered configuration directories\n * \n * @example\n * ```typescript\n * const dirs = await discoverConfigDirectories({\n * configDirName: '.kodrdriv',\n * configFileName: 'config.yaml',\n * maxLevels: 5\n * });\n * // Returns: [\n * // { path: '/project/.kodrdriv', level: 0 },\n * // { path: '/project/parent/.kodrdriv', level: 1 }\n * // ]\n * ```\n */\nexport async function discoverConfigDirectories(\n options: HierarchicalDiscoveryOptions\n): Promise<DiscoveredConfigDir[]> {\n const {\n configDirName,\n maxLevels = 10,\n startingDir = process.cwd(),\n logger\n } = options;\n\n const storage = createStorage({ log: logger?.debug || (() => { }) });\n const discoveredDirs: DiscoveredConfigDir[] = [];\n\n let currentDir = path.resolve(startingDir);\n let level = 0;\n const visited = new Set<string>(); // Prevent infinite loops with symlinks\n\n logger?.debug(`Starting hierarchical discovery from: ${currentDir}`);\n\n while (level < maxLevels) {\n // Prevent infinite loops with symlinks\n const realPath = path.resolve(currentDir);\n if (visited.has(realPath)) {\n logger?.debug(`Already visited ${realPath}, stopping discovery`);\n break;\n }\n visited.add(realPath);\n\n const configDirPath = path.join(currentDir, configDirName);\n logger?.debug(`Checking for config directory: ${configDirPath}`);\n\n try {\n const exists = await storage.exists(configDirPath);\n const isReadable = exists && await storage.isDirectoryReadable(configDirPath);\n\n if (exists && isReadable) {\n discoveredDirs.push({\n path: configDirPath,\n level\n });\n logger?.debug(`Found config directory at level ${level}: ${configDirPath}`);\n } else if (exists && !isReadable) {\n logger?.debug(`Config directory exists but is not readable: ${configDirPath}`);\n }\n } catch (error: any) {\n logger?.debug(`Error checking config directory ${configDirPath}: ${error.message}`);\n }\n\n // Move up one directory level\n const parentDir = path.dirname(currentDir);\n\n // Check if we've reached the root directory\n if (parentDir === currentDir) {\n logger?.debug('Reached filesystem root, stopping discovery');\n break;\n }\n\n currentDir = parentDir;\n level++;\n }\n\n logger?.debug(`Discovery complete. Found ${discoveredDirs.length} config directories`);\n return discoveredDirs;\n}\n\n/**\n * Loads and parses a configuration file from a directory.\n * \n * @param configDir Path to the configuration directory\n * @param configFileName Name of the configuration file\n * @param encoding File encoding\n * @param logger Optional logger\n * @returns Promise resolving to parsed configuration object or null if not found\n */\nexport async function loadConfigFromDirectory(\n configDir: string,\n configFileName: string,\n encoding: string = 'utf8',\n logger?: Logger\n): Promise<object | null> {\n const storage = createStorage({ log: logger?.debug || (() => { }) });\n const configFilePath = path.join(configDir, configFileName);\n\n try {\n logger?.debug(`Attempting to load config file: ${configFilePath}`);\n\n const exists = await storage.exists(configFilePath);\n if (!exists) {\n logger?.debug(`Config file does not exist: ${configFilePath}`);\n return null;\n }\n\n const isReadable = await storage.isFileReadable(configFilePath);\n if (!isReadable) {\n logger?.debug(`Config file exists but is not readable: ${configFilePath}`);\n return null;\n }\n\n const yamlContent = await storage.readFile(configFilePath, encoding);\n const parsedYaml = yaml.load(yamlContent);\n\n if (parsedYaml !== null && typeof parsedYaml === 'object') {\n logger?.debug(`Successfully loaded config from: ${configFilePath}`);\n return parsedYaml as object;\n } else {\n logger?.debug(`Config file contains invalid format: ${configFilePath}`);\n return null;\n }\n } catch (error: any) {\n logger?.debug(`Error loading config from ${configFilePath}: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Deep merges multiple configuration objects with proper precedence.\n * \n * Objects are merged from lowest precedence to highest precedence,\n * meaning that properties in later objects override properties in earlier objects.\n * Arrays are replaced entirely (not merged).\n * \n * @param configs Array of configuration objects, ordered from lowest to highest precedence\n * @returns Merged configuration object\n * \n * @example\n * ```typescript\n * const merged = deepMergeConfigs([\n * { api: { timeout: 5000 }, debug: true }, // Lower precedence\n * { api: { retries: 3 }, features: ['auth'] }, // Higher precedence\n * ]);\n * // Result: { api: { timeout: 5000, retries: 3 }, debug: true, features: ['auth'] }\n * ```\n */\nexport function deepMergeConfigs(configs: object[]): object {\n if (configs.length === 0) {\n return {};\n }\n\n if (configs.length === 1) {\n return { ...configs[0] };\n }\n\n return configs.reduce((merged, current) => {\n return deepMergeTwo(merged, current);\n }, {});\n}\n\n/**\n * Deep merges two objects with proper precedence.\n * \n * @param target Target object (lower precedence)\n * @param source Source object (higher precedence)\n * @returns Merged object\n */\nfunction deepMergeTwo(target: any, source: any): any {\n // Handle null/undefined\n if (source == null) return target;\n if (target == null) return source;\n\n // Handle non-objects (primitives, arrays, functions, etc.)\n if (typeof source !== 'object' || typeof target !== 'object') {\n return source; // Source takes precedence\n }\n\n // Handle arrays - replace entirely, don't merge\n if (Array.isArray(source)) {\n return [...source];\n }\n\n if (Array.isArray(target)) {\n return source; // Source object replaces target array\n }\n\n // Deep merge objects\n const result = { ...target };\n\n for (const key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n if (Object.prototype.hasOwnProperty.call(result, key) &&\n typeof result[key] === 'object' &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key]) &&\n !Array.isArray(result[key])) {\n // Recursively merge nested objects\n result[key] = deepMergeTwo(result[key], source[key]);\n } else {\n // Replace with source value (higher precedence)\n result[key] = source[key];\n }\n }\n }\n\n return result;\n}\n\n/**\n * Loads configurations from multiple directories and merges them with proper precedence.\n * \n * This is the main function for hierarchical configuration loading. It:\n * 1. Discovers configuration directories up the directory tree\n * 2. Loads configuration files from each discovered directory\n * 3. Merges them with proper precedence (closer directories win)\n * 4. Returns the merged configuration with metadata\n * \n * @param options Configuration options for hierarchical loading\n * @returns Promise resolving to hierarchical configuration result\n * \n * @example\n * ```typescript\n * const result = await loadHierarchicalConfig({\n * configDirName: '.kodrdriv',\n * configFileName: 'config.yaml',\n * startingDir: '/project/subdir',\n * maxLevels: 5\n * });\n * \n * // result.config contains merged configuration\n * // result.discoveredDirs shows where configs were found\n * // result.errors contains any non-fatal errors\n * ```\n */\nexport async function loadHierarchicalConfig(\n options: HierarchicalDiscoveryOptions\n): Promise<HierarchicalConfigResult> {\n const { configFileName, encoding = 'utf8', logger } = options;\n\n logger?.debug('Starting hierarchical configuration loading');\n\n // Discover all configuration directories\n const discoveredDirs = await discoverConfigDirectories(options);\n\n if (discoveredDirs.length === 0) {\n logger?.debug('No configuration directories found');\n return {\n config: {},\n discoveredDirs: [],\n errors: []\n };\n }\n\n // Load configurations from each directory\n const configs: object[] = [];\n const errors: string[] = [];\n\n // Sort by level (highest level first = lowest precedence first)\n const sortedDirs = [...discoveredDirs].sort((a, b) => b.level - a.level);\n\n for (const dir of sortedDirs) {\n try {\n const config = await loadConfigFromDirectory(\n dir.path,\n configFileName,\n encoding,\n logger\n );\n\n if (config !== null) {\n configs.push(config);\n logger?.debug(`Loaded config from level ${dir.level}: ${dir.path}`);\n } else {\n logger?.debug(`No valid config found at level ${dir.level}: ${dir.path}`);\n }\n } catch (error: any) {\n const errorMsg = `Failed to load config from ${dir.path}: ${error.message}`;\n errors.push(errorMsg);\n logger?.debug(errorMsg);\n }\n }\n\n // Merge all configurations with proper precedence\n const mergedConfig = deepMergeConfigs(configs);\n\n logger?.debug(`Hierarchical loading complete. Merged ${configs.length} configurations`);\n\n return {\n config: mergedConfig,\n discoveredDirs,\n errors\n };\n} "],"names":["discoverConfigDirectories","options","configDirName","maxLevels","startingDir","process","cwd","logger","storage","createStorage","log","debug","discoveredDirs","currentDir","path","resolve","level","visited","Set","realPath","has","add","configDirPath","join","exists","isReadable","isDirectoryReadable","push","error","message","parentDir","dirname","length","loadConfigFromDirectory","configDir","configFileName","encoding","configFilePath","isFileReadable","yamlContent","readFile","parsedYaml","yaml","load","deepMergeConfigs","configs","reduce","merged","current","deepMergeTwo","target","source","Array","isArray","result","key","Object","prototype","hasOwnProperty","call","loadHierarchicalConfig","config","errors","sortedDirs","sort","a","b","dir","errorMsg","mergedConfig"],"mappings":";;;;AA6CA;;;;;;;;;;;;;;;;;;;;;;;IAwBO,eAAeA,yBAAAA,CAClBC,OAAqC,EAAA;AAErC,IAAA,MAAM,EACFC,aAAa,EACbC,SAAAA,GAAY,EAAE,EACdC,WAAAA,GAAcC,OAAAA,CAAQC,GAAG,EAAE,EAC3BC,MAAM,EACT,GAAGN,OAAAA;AAEJ,IAAA,MAAMO,UAAUC,MAAAA,CAAc;QAAEC,GAAAA,EAAKH,CAAAA,mBAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,MAAK,MAAQ;AAAG,KAAA,CAAA;AAClE,IAAA,MAAMC,iBAAwC,EAAE;IAEhD,IAAIC,UAAAA,GAAaC,aAAAA,CAAKC,OAAO,CAACX,WAAAA,CAAAA;AAC9B,IAAA,IAAIY,KAAAA,GAAQ,CAAA;IACZ,MAAMC,OAAAA,GAAU,IAAIC,GAAAA,EAAAA,CAAAA;AAEpBX,IAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,sCAAsC,EAAEE,UAAAA,CAAAA,CAAY,CAAA;AAEnE,IAAA,MAAOG,QAAQb,SAAAA,CAAW;;QAEtB,MAAMgB,QAAAA,GAAWL,aAAAA,CAAKC,OAAO,CAACF,UAAAA,CAAAA;QAC9B,IAAII,OAAAA,CAAQG,GAAG,CAACD,QAAAA,CAAAA,EAAW;YACvBZ,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,OAAQI,KAAK,CAAC,CAAC,gBAAgB,EAAEQ,QAAAA,CAAS,oBAAoB,CAAC,CAAA;AAC/D,YAAA;AACJ;AACAF,QAAAA,OAAAA,CAAQI,GAAG,CAACF,QAAAA,CAAAA;AAEZ,QAAA,MAAMG,aAAAA,GAAgBR,aAAAA,CAAKS,IAAI,CAACV,UAAAA,EAAYX,aAAAA,CAAAA;AAC5CK,QAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,+BAA+B,EAAEW,aAAAA,CAAAA,CAAe,CAAA;QAE/D,IAAI;AACA,YAAA,MAAME,MAAAA,GAAS,MAAMhB,OAAAA,CAAQgB,MAAM,CAACF,aAAAA,CAAAA;AACpC,YAAA,MAAMG,UAAAA,GAAaD,MAAAA,IAAU,MAAMhB,OAAAA,CAAQkB,mBAAmB,CAACJ,aAAAA,CAAAA;AAE/D,YAAA,IAAIE,UAAUC,UAAAA,EAAY;AACtBb,gBAAAA,cAAAA,CAAee,IAAI,CAAC;oBAChBb,IAAAA,EAAMQ,aAAAA;AACNN,oBAAAA;AACJ,iBAAA,CAAA;gBACAT,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,KAAAA,CAAAA,GAAAA,KAAAA,CAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,gCAAgC,EAAEK,KAAAA,CAAM,EAAE,EAAEM,aAAAA,CAAAA,CAAe,CAAA;aAC9E,MAAO,IAAIE,MAAAA,IAAU,CAACC,UAAAA,EAAY;AAC9BlB,gBAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,6CAA6C,EAAEW,aAAAA,CAAAA,CAAe,CAAA;AACjF;AACJ,SAAA,CAAE,OAAOM,KAAAA,EAAY;AACjBrB,YAAAA,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,gCAAgC,EAAEW,aAAAA,CAAc,EAAE,EAAEM,KAAAA,CAAMC,OAAO,CAAA,CAAE,CAAA;AACtF;;QAGA,MAAMC,SAAAA,GAAYhB,aAAAA,CAAKiB,OAAO,CAAClB,UAAAA,CAAAA;;AAG/B,QAAA,IAAIiB,cAAcjB,UAAAA,EAAY;YAC1BN,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,6CAAA,CAAA;AACd,YAAA;AACJ;QAEAE,UAAAA,GAAaiB,SAAAA;AACbd,QAAAA,KAAAA,EAAAA;AACJ;IAEAT,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,0BAA0B,EAAEC,cAAAA,CAAeoB,MAAM,CAAC,mBAAmB,CAAC,CAAA;IACrF,OAAOpB,cAAAA;AACX;AAEA;;;;;;;;IASO,eAAeqB,uBAAAA,CAClBC,SAAiB,EACjBC,cAAsB,EACtBC,QAAAA,GAAmB,MAAM,EACzB7B,MAAe,EAAA;AAEf,IAAA,MAAMC,UAAUC,MAAAA,CAAc;QAAEC,GAAAA,EAAKH,CAAAA,mBAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,MAAK,MAAQ;AAAG,KAAA,CAAA;AAClE,IAAA,MAAM0B,cAAAA,GAAiBvB,aAAAA,CAAKS,IAAI,CAACW,SAAAA,EAAWC,cAAAA,CAAAA;IAE5C,IAAI;AACA5B,QAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,gCAAgC,EAAE0B,cAAAA,CAAAA,CAAgB,CAAA;AAEjE,QAAA,MAAMb,MAAAA,GAAS,MAAMhB,OAAAA,CAAQgB,MAAM,CAACa,cAAAA,CAAAA;AACpC,QAAA,IAAI,CAACb,MAAAA,EAAQ;AACTjB,YAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,4BAA4B,EAAE0B,cAAAA,CAAAA,CAAgB,CAAA;YAC7D,OAAO,IAAA;AACX;AAEA,QAAA,MAAMZ,UAAAA,GAAa,MAAMjB,OAAAA,CAAQ8B,cAAc,CAACD,cAAAA,CAAAA;AAChD,QAAA,IAAI,CAACZ,UAAAA,EAAY;AACblB,YAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,wCAAwC,EAAE0B,cAAAA,CAAAA,CAAgB,CAAA;YACzE,OAAO,IAAA;AACX;AAEA,QAAA,MAAME,WAAAA,GAAc,MAAM/B,OAAAA,CAAQgC,QAAQ,CAACH,cAAAA,EAAgBD,QAAAA,CAAAA;QAC3D,MAAMK,UAAAA,GAAaC,IAAAA,CAAKC,IAAI,CAACJ,WAAAA,CAAAA;AAE7B,QAAA,IAAIE,UAAAA,KAAe,IAAA,IAAQ,OAAOA,UAAAA,KAAe,QAAA,EAAU;AACvDlC,YAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,iCAAiC,EAAE0B,cAAAA,CAAAA,CAAgB,CAAA;YAClE,OAAOI,UAAAA;SACX,MAAO;AACHlC,YAAAA,MAAAA,KAAAA,IAAAA,IAAAA,6BAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,qCAAqC,EAAE0B,cAAAA,CAAAA,CAAgB,CAAA;YACtE,OAAO,IAAA;AACX;AACJ,KAAA,CAAE,OAAOT,KAAAA,EAAY;AACjBrB,QAAAA,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,0BAA0B,EAAE0B,cAAAA,CAAe,EAAE,EAAET,KAAAA,CAAMC,OAAO,CAAA,CAAE,CAAA;QAC7E,OAAO,IAAA;AACX;AACJ;AAEA;;;;;;;;;;;;;;;;;;IAmBO,SAASe,gBAAAA,CAAiBC,OAAiB,EAAA;IAC9C,IAAIA,OAAAA,CAAQb,MAAM,KAAK,CAAA,EAAG;AACtB,QAAA,OAAO,EAAC;AACZ;IAEA,IAAIa,OAAAA,CAAQb,MAAM,KAAK,CAAA,EAAG;QACtB,OAAO;YAAE,GAAGa,OAAO,CAAC,CAAA;AAAG,SAAA;AAC3B;AAEA,IAAA,OAAOA,OAAAA,CAAQC,MAAM,CAAC,CAACC,MAAAA,EAAQC,OAAAA,GAAAA;AAC3B,QAAA,OAAOC,aAAaF,MAAAA,EAAQC,OAAAA,CAAAA;AAChC,KAAA,EAAG,EAAC,CAAA;AACR;AAEA;;;;;;AAMC,IACD,SAASC,YAAAA,CAAaC,MAAW,EAAEC,MAAW,EAAA;;IAE1C,IAAIA,MAAAA,IAAU,MAAM,OAAOD,MAAAA;IAC3B,IAAIA,MAAAA,IAAU,MAAM,OAAOC,MAAAA;;AAG3B,IAAA,IAAI,OAAOA,MAAAA,KAAW,QAAA,IAAY,OAAOD,WAAW,QAAA,EAAU;AAC1D,QAAA,OAAOC;AACX;;IAGA,IAAIC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QACvB,OAAO;AAAIA,YAAAA,GAAAA;AAAO,SAAA;AACtB;IAEA,IAAIC,KAAAA,CAAMC,OAAO,CAACH,MAAAA,CAAAA,EAAS;AACvB,QAAA,OAAOC;AACX;;AAGA,IAAA,MAAMG,MAAAA,GAAS;AAAE,QAAA,GAAGJ;AAAO,KAAA;IAE3B,IAAK,MAAMK,OAAOJ,MAAAA,CAAQ;QACtB,IAAIK,MAAAA,CAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACR,QAAQI,GAAAA,CAAAA,EAAM;AACnD,YAAA,IAAIC,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACL,MAAAA,EAAQC,GAAAA,CAAAA,IAC7C,OAAOD,MAAM,CAACC,GAAAA,CAAI,KAAK,QAAA,IACvB,OAAOJ,MAAM,CAACI,GAAAA,CAAI,KAAK,YACvB,CAACH,KAAAA,CAAMC,OAAO,CAACF,MAAM,CAACI,GAAAA,CAAI,CAAA,IAC1B,CAACH,MAAMC,OAAO,CAACC,MAAM,CAACC,IAAI,CAAA,EAAG;;gBAE7BD,MAAM,CAACC,GAAAA,CAAI,GAAGN,YAAAA,CAAaK,MAAM,CAACC,GAAAA,CAAI,EAAEJ,MAAM,CAACI,GAAAA,CAAI,CAAA;aACvD,MAAO;;AAEHD,gBAAAA,MAAM,CAACC,GAAAA,CAAI,GAAGJ,MAAM,CAACI,GAAAA,CAAI;AAC7B;AACJ;AACJ;IAEA,OAAOD,MAAAA;AACX;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;IA0BO,eAAeM,sBAAAA,CAClB3D,OAAqC,EAAA;IAErC,MAAM,EAAEkC,cAAc,EAAEC,QAAAA,GAAW,MAAM,EAAE7B,MAAM,EAAE,GAAGN,OAAAA;IAEtDM,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,6CAAA,CAAA;;IAGd,MAAMC,cAAAA,GAAiB,MAAMZ,yBAAAA,CAA0BC,OAAAA,CAAAA;IAEvD,IAAIW,cAAAA,CAAeoB,MAAM,KAAK,CAAA,EAAG;QAC7BzB,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,oCAAA,CAAA;QACd,OAAO;AACHkD,YAAAA,MAAAA,EAAQ,EAAC;AACTjD,YAAAA,cAAAA,EAAgB,EAAE;AAClBkD,YAAAA,MAAAA,EAAQ;AACZ,SAAA;AACJ;;AAGA,IAAA,MAAMjB,UAAoB,EAAE;AAC5B,IAAA,MAAMiB,SAAmB,EAAE;;AAG3B,IAAA,MAAMC,UAAAA,GAAa;AAAInD,QAAAA,GAAAA;KAAe,CAACoD,IAAI,CAAC,CAACC,CAAAA,EAAGC,IAAMA,CAAAA,CAAElD,KAAK,GAAGiD,CAAAA,CAAEjD,KAAK,CAAA;IAEvE,KAAK,MAAMmD,OAAOJ,UAAAA,CAAY;QAC1B,IAAI;AACA,YAAA,MAAMF,SAAS,MAAM5B,uBAAAA,CACjBkC,IAAIrD,IAAI,EACRqB,gBACAC,QAAAA,EACA7B,MAAAA,CAAAA;AAGJ,YAAA,IAAIsD,WAAW,IAAA,EAAM;AACjBhB,gBAAAA,OAAAA,CAAQlB,IAAI,CAACkC,MAAAA,CAAAA;AACbtD,gBAAAA,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,KAAAA,CAAAA,GAAAA,KAAAA,CAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,yBAAyB,EAAEwD,GAAAA,CAAInD,KAAK,CAAC,EAAE,EAAEmD,GAAAA,CAAIrD,IAAI,CAAA,CAAE,CAAA;aACtE,MAAO;AACHP,gBAAAA,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,KAAAA,CAAAA,GAAAA,KAAAA,CAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,+BAA+B,EAAEwD,GAAAA,CAAInD,KAAK,CAAC,EAAE,EAAEmD,GAAAA,CAAIrD,IAAI,CAAA,CAAE,CAAA;AAC5E;AACJ,SAAA,CAAE,OAAOc,KAAAA,EAAY;YACjB,MAAMwC,QAAAA,GAAW,CAAC,2BAA2B,EAAED,GAAAA,CAAIrD,IAAI,CAAC,EAAE,EAAEc,KAAAA,CAAMC,OAAO,CAAA,CAAE;AAC3EiC,YAAAA,MAAAA,CAAOnC,IAAI,CAACyC,QAAAA,CAAAA;YACZ7D,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAACyD,QAAAA,CAAAA;AAClB;AACJ;;AAGA,IAAA,MAAMC,eAAezB,gBAAAA,CAAiBC,OAAAA,CAAAA;IAEtCtC,MAAAA,KAAAA,IAAAA,IAAAA,MAAAA,KAAAA,MAAAA,GAAAA,MAAAA,GAAAA,MAAAA,CAAQI,KAAK,CAAC,CAAC,sCAAsC,EAAEkC,OAAAA,CAAQb,MAAM,CAAC,eAAe,CAAC,CAAA;IAEtF,OAAO;QACH6B,MAAAA,EAAQQ,YAAAA;AACRzD,QAAAA,cAAAA;AACAkD,QAAAA;AACJ,KAAA;AACJ;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theunwalked/cardigantime",
3
- "version": "0.0.3",
3
+ "version": "0.0.6",
4
4
  "description": "cardigantime is a tool to help you time your cardigans.",
5
5
  "type": "module",
6
6
  "main": "./dist/cardigantime.cjs",
@@ -46,9 +46,9 @@
46
46
  "@swc/core": "^1.12.7",
47
47
  "@types/js-yaml": "^4.0.9",
48
48
  "@types/luxon": "^3.6.2",
49
- "@types/node": "^24.0.7",
50
- "@typescript-eslint/eslint-plugin": "^8.35.0",
51
- "@typescript-eslint/parser": "^8.35.0",
49
+ "@types/node": "^24.0.8",
50
+ "@typescript-eslint/eslint-plugin": "^8.35.1",
51
+ "@typescript-eslint/parser": "^8.35.1",
52
52
  "@vitest/coverage-v8": "^3.2.4",
53
53
  "eslint": "^9.30.0",
54
54
  "eslint-plugin-import": "^2.32.0",
@@ -67,6 +67,11 @@
67
67
  "test": "vitest run --coverage",
68
68
  "lint": "eslint . --ext .ts",
69
69
  "lint:fix": "eslint . --ext .ts --fix",
70
- "clean": "rm -rf dist"
70
+ "clean": "rm -rf dist",
71
+ "docs:dev": "cd docs && cp ../README.md public/ && pnpm install && pnpm run dev",
72
+ "docs:build": "cd docs && cp ../README.md public/ && pnpm install && pnpm run build",
73
+ "docs:preview": "cd docs && pnpm run preview",
74
+ "docs:test": "cd docs && pnpm install && pnpm run test",
75
+ "docs:coverage": "cd docs && pnpm install && pnpm run coverage"
71
76
  }
72
77
  }