@theunwalked/cardigantime 0.0.5 → 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.
package/README.md CHANGED
@@ -240,7 +240,7 @@ debug: true
240
240
  Cardigantime merges configuration from multiple sources in this order (highest to lowest priority):
241
241
 
242
242
  1. **Command-line arguments** (highest priority)
243
- 2. **Configuration file** (medium priority)
243
+ 2. **Configuration file(s)** (medium priority)
244
244
  3. **Default values** (lowest priority)
245
245
 
246
246
  ```typescript
@@ -255,7 +255,105 @@ Cardigantime merges configuration from multiple sources in this order (highest t
255
255
  // debug: false (from file)
256
256
  ```
257
257
 
258
- ### 2. Schema Validation
258
+ ### 2. Hierarchical Configuration Discovery
259
+
260
+ Cardigantime supports hierarchical configuration discovery, similar to how tools like `.gitignore`, `.eslintrc`, or `package.json` work. When the `hierarchical` feature is enabled, Cardigantime will:
261
+
262
+ 1. **Start from the specified config directory** (e.g., `./project/subdir/.kodrdriv`)
263
+ 2. **Search up the directory tree** for additional config directories with the same name
264
+ 3. **Merge configurations** with proper precedence (closer directories win)
265
+ 4. **Apply CLI arguments** as the final override
266
+
267
+ #### Example Directory Structure
268
+
269
+ ```
270
+ /home/user/projects/
271
+ ├── .kodrdriv/
272
+ │ └── config.yaml # Root-level config
273
+ ├── myproject/
274
+ │ ├── .kodrdriv/
275
+ │ │ └── config.yaml # Project-level config
276
+ │ └── submodule/
277
+ │ ├── .kodrdriv/
278
+ │ │ └── config.yaml # Submodule-level config
279
+ │ └── my-script.js
280
+ ```
281
+
282
+ #### Hierarchical Discovery Behavior
283
+
284
+ When running from `/home/user/projects/myproject/submodule/` with hierarchical discovery:
285
+
286
+ 1. **Level 0 (Highest Priority)**: `/home/user/projects/myproject/submodule/.kodrdriv/config.yaml`
287
+ 2. **Level 1**: `/home/user/projects/myproject/.kodrdriv/config.yaml`
288
+ 3. **Level 2 (Lowest Priority)**: `/home/user/projects/.kodrdriv/config.yaml`
289
+
290
+ Configurations are deep-merged, with closer directories taking precedence:
291
+
292
+ ```yaml
293
+ # /home/user/projects/.kodrdriv/config.yaml (Level 2)
294
+ database:
295
+ host: localhost
296
+ port: 5432
297
+ ssl: false
298
+ logging:
299
+ level: info
300
+
301
+ # /home/user/projects/myproject/.kodrdriv/config.yaml (Level 1)
302
+ database:
303
+ port: 5433
304
+ ssl: true
305
+ api:
306
+ timeout: 5000
307
+
308
+ # /home/user/projects/myproject/submodule/.kodrdriv/config.yaml (Level 0)
309
+ database:
310
+ host: dev.example.com
311
+ logging:
312
+ level: debug
313
+
314
+ # Final merged configuration:
315
+ database:
316
+ host: dev.example.com # From Level 0 (highest precedence)
317
+ port: 5433 # From Level 1
318
+ ssl: true # From Level 1
319
+ api:
320
+ timeout: 5000 # From Level 1
321
+ logging:
322
+ level: debug # From Level 0 (highest precedence)
323
+ ```
324
+
325
+ #### Enabling Hierarchical Discovery
326
+
327
+ ```typescript
328
+ const cardigantime = create({
329
+ defaults: {
330
+ configDirectory: '.kodrdriv',
331
+ configFile: 'config.yaml'
332
+ },
333
+ configShape: MyConfigSchema.shape,
334
+ features: ['config', 'hierarchical'], // Enable hierarchical discovery
335
+ });
336
+ ```
337
+
338
+ #### Hierarchical Discovery Options
339
+
340
+ The hierarchical discovery has several built-in protections and features:
341
+
342
+ - **Maximum traversal depth**: Prevents infinite loops (default: 10 levels)
343
+ - **Symlink protection**: Tracks visited paths to prevent circular references
344
+ - **Graceful fallback**: Falls back to single-directory mode if discovery fails
345
+ - **Error tolerance**: Continues discovery even if some directories are unreadable
346
+ - **Root detection**: Automatically stops at filesystem root
347
+
348
+ #### Use Cases for Hierarchical Configuration
349
+
350
+ 1. **Monorepos**: Share common configuration across multiple packages
351
+ 2. **Project inheritance**: Override team/organization defaults for specific projects
352
+ 3. **Environment layering**: Different configs for development/staging/production
353
+ 4. **Tool configuration**: Similar to how ESLint or Prettier find configs up the tree
354
+ 5. **Multi-tenant applications**: Tenant-specific overrides of global settings
355
+
356
+ ### 3. Schema Validation
259
357
 
260
358
  All configuration is validated against your Zod schema:
261
359
 
@@ -276,7 +374,7 @@ const cardigantime = create({
276
374
  });
277
375
  ```
278
376
 
279
- ### 3. Type Safety
377
+ ### 4. Type Safety
280
378
 
281
379
  Cardigantime provides full TypeScript support:
282
380
 
@@ -293,7 +391,7 @@ if (config.features.includes('auth')) {
293
391
  }
294
392
  ```
295
393
 
296
- ### 4. Error Handling
394
+ ### 5. Error Handling
297
395
 
298
396
  Cardigantime provides detailed error messages for common issues:
299
397
 
@@ -365,6 +463,129 @@ Sets a custom logger for debugging and error reporting.
365
463
 
366
464
  ## Advanced Usage
367
465
 
466
+ ### Hierarchical Configuration Discovery
467
+
468
+ Here's a complete example of using hierarchical configuration discovery for a monorepo setup:
469
+
470
+ ```typescript
471
+ import { create } from '@theunwalked/cardigantime';
472
+ import { z } from 'zod';
473
+
474
+ // Define a comprehensive configuration schema
475
+ const ProjectConfigSchema = z.object({
476
+ projectName: z.string(),
477
+ environment: z.enum(['development', 'staging', 'production']).default('development'),
478
+ database: z.object({
479
+ host: z.string().default('localhost'),
480
+ port: z.number().default(5432),
481
+ ssl: z.boolean().default(false),
482
+ maxConnections: z.number().default(10),
483
+ }),
484
+ api: z.object({
485
+ baseUrl: z.string().url(),
486
+ timeout: z.number().default(5000),
487
+ retries: z.number().default(3),
488
+ }),
489
+ features: z.record(z.boolean()).default({}),
490
+ logging: z.object({
491
+ level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
492
+ outputs: z.array(z.string()).default(['console']),
493
+ }),
494
+ });
495
+
496
+ // Enable hierarchical discovery
497
+ const cardigantime = create({
498
+ defaults: {
499
+ configDirectory: '.myapp',
500
+ configFile: 'config.yaml',
501
+ },
502
+ configShape: ProjectConfigSchema.shape,
503
+ features: ['config', 'hierarchical'], // Enable hierarchical discovery
504
+ });
505
+
506
+ // Usage in a CLI tool
507
+ async function setupProject() {
508
+ try {
509
+ const config = await cardigantime.read(process.argv);
510
+ await cardigantime.validate(config);
511
+
512
+ console.log(`Setting up ${config.projectName} in ${config.environment} mode`);
513
+ console.log(`Database: ${config.database.host}:${config.database.port}`);
514
+ console.log(`API: ${config.api.baseUrl}`);
515
+
516
+ return config;
517
+ } catch (error) {
518
+ console.error('Configuration error:', error.message);
519
+ process.exit(1);
520
+ }
521
+ }
522
+ ```
523
+
524
+ **Directory Structure:**
525
+ ```
526
+ /workspace/
527
+ ├── .myapp/
528
+ │ └── config.yaml # Global defaults
529
+ ├── team-frontend/
530
+ │ ├── .myapp/
531
+ │ │ └── config.yaml # Team-specific settings
532
+ │ ├── app1/
533
+ │ │ ├── .myapp/
534
+ │ │ │ └── config.yaml # App-specific overrides
535
+ │ │ └── package.json
536
+ │ └── app2/
537
+ │ └── package.json # Uses team + global config
538
+ ```
539
+
540
+ **Configuration Files:**
541
+ ```yaml
542
+ # /workspace/.myapp/config.yaml (Global)
543
+ database:
544
+ host: prod.db.company.com
545
+ ssl: true
546
+ api:
547
+ baseUrl: https://api.company.com
548
+ logging:
549
+ level: warn
550
+ outputs: [console, file]
551
+
552
+ # /workspace/team-frontend/.myapp/config.yaml (Team)
553
+ database:
554
+ host: team-frontend.db.company.com
555
+ api:
556
+ timeout: 3000
557
+ features:
558
+ analytics: true
559
+ darkMode: true
560
+
561
+ # /workspace/team-frontend/app1/.myapp/config.yaml (App)
562
+ projectName: frontend-app1
563
+ environment: development
564
+ database:
565
+ host: localhost # Override for local development
566
+ logging:
567
+ level: debug
568
+ ```
569
+
570
+ When running from `/workspace/team-frontend/app1/`, the final merged configuration will be:
571
+
572
+ ```yaml
573
+ projectName: frontend-app1 # From app level
574
+ environment: development # From app level
575
+ database:
576
+ host: localhost # From app level (highest precedence)
577
+ ssl: true # From global level
578
+ api:
579
+ baseUrl: https://api.company.com # From global level
580
+ timeout: 3000 # From team level
581
+ features:
582
+ analytics: true # From team level
583
+ darkMode: true # From team level
584
+ logging:
585
+ level: debug # From app level (highest precedence)
586
+ outputs: [console, file] # From global level
587
+ ```
588
+
368
589
  ### Custom Logger
369
590
 
370
591
  ```typescript
@@ -415,6 +415,257 @@ const create$1 = (params)=>{
415
415
  };
416
416
  };
417
417
 
418
+ /**
419
+ * Discovers configuration directories by traversing up the directory tree.
420
+ *
421
+ * Starting from the specified directory (or current working directory),
422
+ * this function searches for directories with the given name, continuing
423
+ * up the directory tree until it reaches the filesystem root or the
424
+ * maximum number of levels.
425
+ *
426
+ * @param options Configuration options for discovery
427
+ * @returns Promise resolving to array of discovered configuration directories
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * const dirs = await discoverConfigDirectories({
432
+ * configDirName: '.kodrdriv',
433
+ * configFileName: 'config.yaml',
434
+ * maxLevels: 5
435
+ * });
436
+ * // Returns: [
437
+ * // { path: '/project/.kodrdriv', level: 0 },
438
+ * // { path: '/project/parent/.kodrdriv', level: 1 }
439
+ * // ]
440
+ * ```
441
+ */ async function discoverConfigDirectories(options) {
442
+ const { configDirName, maxLevels = 10, startingDir = process.cwd(), logger } = options;
443
+ const storage = create$1({
444
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
445
+ });
446
+ const discoveredDirs = [];
447
+ let currentDir = path.resolve(startingDir);
448
+ let level = 0;
449
+ const visited = new Set(); // Prevent infinite loops with symlinks
450
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Starting hierarchical discovery from: ${currentDir}`);
451
+ while(level < maxLevels){
452
+ // Prevent infinite loops with symlinks
453
+ const realPath = path.resolve(currentDir);
454
+ if (visited.has(realPath)) {
455
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Already visited ${realPath}, stopping discovery`);
456
+ break;
457
+ }
458
+ visited.add(realPath);
459
+ const configDirPath = path.join(currentDir, configDirName);
460
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Checking for config directory: ${configDirPath}`);
461
+ try {
462
+ const exists = await storage.exists(configDirPath);
463
+ const isReadable = exists && await storage.isDirectoryReadable(configDirPath);
464
+ if (exists && isReadable) {
465
+ discoveredDirs.push({
466
+ path: configDirPath,
467
+ level
468
+ });
469
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Found config directory at level ${level}: ${configDirPath}`);
470
+ } else if (exists && !isReadable) {
471
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config directory exists but is not readable: ${configDirPath}`);
472
+ }
473
+ } catch (error) {
474
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error checking config directory ${configDirPath}: ${error.message}`);
475
+ }
476
+ // Move up one directory level
477
+ const parentDir = path.dirname(currentDir);
478
+ // Check if we've reached the root directory
479
+ if (parentDir === currentDir) {
480
+ logger === null || logger === void 0 ? void 0 : logger.debug('Reached filesystem root, stopping discovery');
481
+ break;
482
+ }
483
+ currentDir = parentDir;
484
+ level++;
485
+ }
486
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Discovery complete. Found ${discoveredDirs.length} config directories`);
487
+ return discoveredDirs;
488
+ }
489
+ /**
490
+ * Loads and parses a configuration file from a directory.
491
+ *
492
+ * @param configDir Path to the configuration directory
493
+ * @param configFileName Name of the configuration file
494
+ * @param encoding File encoding
495
+ * @param logger Optional logger
496
+ * @returns Promise resolving to parsed configuration object or null if not found
497
+ */ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger) {
498
+ const storage = create$1({
499
+ log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
500
+ });
501
+ const configFilePath = path.join(configDir, configFileName);
502
+ try {
503
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Attempting to load config file: ${configFilePath}`);
504
+ const exists = await storage.exists(configFilePath);
505
+ if (!exists) {
506
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file does not exist: ${configFilePath}`);
507
+ return null;
508
+ }
509
+ const isReadable = await storage.isFileReadable(configFilePath);
510
+ if (!isReadable) {
511
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file exists but is not readable: ${configFilePath}`);
512
+ return null;
513
+ }
514
+ const yamlContent = await storage.readFile(configFilePath, encoding);
515
+ const parsedYaml = yaml__namespace.load(yamlContent);
516
+ if (parsedYaml !== null && typeof parsedYaml === 'object') {
517
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Successfully loaded config from: ${configFilePath}`);
518
+ return parsedYaml;
519
+ } else {
520
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Config file contains invalid format: ${configFilePath}`);
521
+ return null;
522
+ }
523
+ } catch (error) {
524
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Error loading config from ${configFilePath}: ${error.message}`);
525
+ return null;
526
+ }
527
+ }
528
+ /**
529
+ * Deep merges multiple configuration objects with proper precedence.
530
+ *
531
+ * Objects are merged from lowest precedence to highest precedence,
532
+ * meaning that properties in later objects override properties in earlier objects.
533
+ * Arrays are replaced entirely (not merged).
534
+ *
535
+ * @param configs Array of configuration objects, ordered from lowest to highest precedence
536
+ * @returns Merged configuration object
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * const merged = deepMergeConfigs([
541
+ * { api: { timeout: 5000 }, debug: true }, // Lower precedence
542
+ * { api: { retries: 3 }, features: ['auth'] }, // Higher precedence
543
+ * ]);
544
+ * // Result: { api: { timeout: 5000, retries: 3 }, debug: true, features: ['auth'] }
545
+ * ```
546
+ */ function deepMergeConfigs(configs) {
547
+ if (configs.length === 0) {
548
+ return {};
549
+ }
550
+ if (configs.length === 1) {
551
+ return {
552
+ ...configs[0]
553
+ };
554
+ }
555
+ return configs.reduce((merged, current)=>{
556
+ return deepMergeTwo(merged, current);
557
+ }, {});
558
+ }
559
+ /**
560
+ * Deep merges two objects with proper precedence.
561
+ *
562
+ * @param target Target object (lower precedence)
563
+ * @param source Source object (higher precedence)
564
+ * @returns Merged object
565
+ */ function deepMergeTwo(target, source) {
566
+ // Handle null/undefined
567
+ if (source == null) return target;
568
+ if (target == null) return source;
569
+ // Handle non-objects (primitives, arrays, functions, etc.)
570
+ if (typeof source !== 'object' || typeof target !== 'object') {
571
+ return source; // Source takes precedence
572
+ }
573
+ // Handle arrays - replace entirely, don't merge
574
+ if (Array.isArray(source)) {
575
+ return [
576
+ ...source
577
+ ];
578
+ }
579
+ if (Array.isArray(target)) {
580
+ return source; // Source object replaces target array
581
+ }
582
+ // Deep merge objects
583
+ const result = {
584
+ ...target
585
+ };
586
+ for(const key in source){
587
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
588
+ if (Object.prototype.hasOwnProperty.call(result, key) && typeof result[key] === 'object' && typeof source[key] === 'object' && !Array.isArray(source[key]) && !Array.isArray(result[key])) {
589
+ // Recursively merge nested objects
590
+ result[key] = deepMergeTwo(result[key], source[key]);
591
+ } else {
592
+ // Replace with source value (higher precedence)
593
+ result[key] = source[key];
594
+ }
595
+ }
596
+ }
597
+ return result;
598
+ }
599
+ /**
600
+ * Loads configurations from multiple directories and merges them with proper precedence.
601
+ *
602
+ * This is the main function for hierarchical configuration loading. It:
603
+ * 1. Discovers configuration directories up the directory tree
604
+ * 2. Loads configuration files from each discovered directory
605
+ * 3. Merges them with proper precedence (closer directories win)
606
+ * 4. Returns the merged configuration with metadata
607
+ *
608
+ * @param options Configuration options for hierarchical loading
609
+ * @returns Promise resolving to hierarchical configuration result
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * const result = await loadHierarchicalConfig({
614
+ * configDirName: '.kodrdriv',
615
+ * configFileName: 'config.yaml',
616
+ * startingDir: '/project/subdir',
617
+ * maxLevels: 5
618
+ * });
619
+ *
620
+ * // result.config contains merged configuration
621
+ * // result.discoveredDirs shows where configs were found
622
+ * // result.errors contains any non-fatal errors
623
+ * ```
624
+ */ async function loadHierarchicalConfig(options) {
625
+ const { configFileName, encoding = 'utf8', logger } = options;
626
+ logger === null || logger === void 0 ? void 0 : logger.debug('Starting hierarchical configuration loading');
627
+ // Discover all configuration directories
628
+ const discoveredDirs = await discoverConfigDirectories(options);
629
+ if (discoveredDirs.length === 0) {
630
+ logger === null || logger === void 0 ? void 0 : logger.debug('No configuration directories found');
631
+ return {
632
+ config: {},
633
+ discoveredDirs: [],
634
+ errors: []
635
+ };
636
+ }
637
+ // Load configurations from each directory
638
+ const configs = [];
639
+ const errors = [];
640
+ // Sort by level (highest level first = lowest precedence first)
641
+ const sortedDirs = [
642
+ ...discoveredDirs
643
+ ].sort((a, b)=>b.level - a.level);
644
+ for (const dir of sortedDirs){
645
+ try {
646
+ const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger);
647
+ if (config !== null) {
648
+ configs.push(config);
649
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Loaded config from level ${dir.level}: ${dir.path}`);
650
+ } else {
651
+ logger === null || logger === void 0 ? void 0 : logger.debug(`No valid config found at level ${dir.level}: ${dir.path}`);
652
+ }
653
+ } catch (error) {
654
+ const errorMsg = `Failed to load config from ${dir.path}: ${error.message}`;
655
+ errors.push(errorMsg);
656
+ logger === null || logger === void 0 ? void 0 : logger.debug(errorMsg);
657
+ }
658
+ }
659
+ // Merge all configurations with proper precedence
660
+ const mergedConfig = deepMergeConfigs(configs);
661
+ logger === null || logger === void 0 ? void 0 : logger.debug(`Hierarchical loading complete. Merged ${configs.length} configurations`);
662
+ return {
663
+ config: mergedConfig,
664
+ discoveredDirs,
665
+ errors
666
+ };
667
+ }
668
+
418
669
  /**
419
670
  * Removes undefined values from an object to create a clean configuration.
420
671
  * This is used to merge configuration sources while avoiding undefined pollution.
@@ -509,15 +760,70 @@ const create$1 = (params)=>{
509
760
  */ const read = async (args, options)=>{
510
761
  var _options_defaults;
511
762
  const logger = options.logger;
512
- const storage = create$1({
513
- log: logger.debug
514
- });
515
763
  const rawConfigDir = args.configDirectory || ((_options_defaults = options.defaults) === null || _options_defaults === void 0 ? void 0 : _options_defaults.configDirectory);
516
764
  if (!rawConfigDir) {
517
765
  throw new Error('Configuration directory must be specified');
518
766
  }
519
767
  const resolvedConfigDir = validateConfigDirectory$1(rawConfigDir);
520
768
  logger.debug('Resolved config directory');
769
+ let rawFileConfig = {};
770
+ // Check if hierarchical configuration discovery is enabled
771
+ if (options.features.includes('hierarchical')) {
772
+ logger.debug('Hierarchical configuration discovery enabled');
773
+ try {
774
+ // Extract the config directory name from the path for hierarchical discovery
775
+ const configDirName = path__namespace.basename(resolvedConfigDir);
776
+ const startingDir = path__namespace.dirname(resolvedConfigDir);
777
+ logger.debug(`Using hierarchical discovery: configDirName=${configDirName}, startingDir=${startingDir}`);
778
+ const hierarchicalResult = await loadHierarchicalConfig({
779
+ configDirName,
780
+ configFileName: options.defaults.configFile,
781
+ startingDir,
782
+ encoding: options.defaults.encoding,
783
+ logger
784
+ });
785
+ rawFileConfig = hierarchicalResult.config;
786
+ if (hierarchicalResult.discoveredDirs.length > 0) {
787
+ logger.debug(`Hierarchical discovery found ${hierarchicalResult.discoveredDirs.length} configuration directories`);
788
+ hierarchicalResult.discoveredDirs.forEach((dir)=>{
789
+ logger.debug(` Level ${dir.level}: ${dir.path}`);
790
+ });
791
+ } else {
792
+ logger.debug('No configuration directories found in hierarchy');
793
+ }
794
+ if (hierarchicalResult.errors.length > 0) {
795
+ hierarchicalResult.errors.forEach((error)=>logger.warn(`Hierarchical config warning: ${error}`));
796
+ }
797
+ } catch (error) {
798
+ logger.error('Hierarchical configuration loading failed: ' + (error.message || 'Unknown error'));
799
+ // Fall back to single directory mode
800
+ logger.debug('Falling back to single directory configuration loading');
801
+ rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
802
+ }
803
+ } else {
804
+ // Use traditional single directory configuration loading
805
+ logger.debug('Using single directory configuration loading');
806
+ rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
807
+ }
808
+ const config = clean({
809
+ ...rawFileConfig,
810
+ ...{
811
+ configDirectory: resolvedConfigDir
812
+ }
813
+ });
814
+ return config;
815
+ };
816
+ /**
817
+ * Loads configuration from a single directory (traditional mode).
818
+ *
819
+ * @param resolvedConfigDir - The resolved configuration directory path
820
+ * @param options - Cardigantime options
821
+ * @param logger - Logger instance
822
+ * @returns Promise resolving to the configuration object
823
+ */ async function loadSingleDirectoryConfig(resolvedConfigDir, options, logger) {
824
+ const storage = create$1({
825
+ log: logger.debug
826
+ });
521
827
  const configFile = validatePath(options.defaults.configFile, resolvedConfigDir);
522
828
  logger.debug('Attempting to load config file for cardigantime');
523
829
  let rawFileConfig = {};
@@ -539,14 +845,8 @@ const create$1 = (params)=>{
539
845
  logger.error('Failed to load or parse configuration file: ' + (error.message || 'Unknown error'));
540
846
  }
541
847
  }
542
- const config = clean({
543
- ...rawFileConfig,
544
- ...{
545
- configDirectory: resolvedConfigDir
546
- }
547
- });
548
- return config;
549
- };
848
+ return rawFileConfig;
849
+ }
550
850
 
551
851
  /**
552
852
  * Error thrown when configuration validation fails