@theunwalked/cardigantime 0.0.9 → 0.0.11

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
@@ -7,7 +7,7 @@ A robust TypeScript library for configuration management in command-line applica
7
7
  Cardigantime is a configuration management library designed to solve the common problem of handling configuration in CLI applications. It provides a unified way to:
8
8
 
9
9
  - **Read configuration from YAML files** with intelligent file discovery
10
- - **Validate configuration** using Zod schemas for type safetygit sta
10
+ - **Validate configuration** using Zod schemas for type safety
11
11
  - **Integrate with CLI frameworks** like Commander.js seamlessly
12
12
  - **Merge configuration sources** (files, CLI args, defaults) with proper precedence
13
13
  - **Handle errors gracefully** with comprehensive logging and user-friendly error messages
@@ -16,119 +16,13 @@ Cardigantime is a configuration management library designed to solve the common
16
16
 
17
17
  Building CLI applications with proper configuration management is harder than it should be. **Cardigantime was created specifically to solve the complex problem of supporting sophisticated configuration systems that seamlessly merge command-line arguments, configuration files, and default values.**
18
18
 
19
- ### The Configuration Complexity Problem
19
+ Without Cardigantime, you need to manually handle:
20
+ - Multi-layered configuration sources with proper precedence
21
+ - Nested configuration objects with deep validation
22
+ - Type safety throughout the configuration pipeline
23
+ - Graceful error handling with actionable messages
20
24
 
21
- Modern CLI applications need to handle increasingly complex configuration scenarios:
22
-
23
- - **Multi-layered configuration sources** with proper precedence (CLI args > config files > defaults)
24
- - **Nested configuration objects** with deep validation requirements
25
- - **Environment-specific configurations** (development, staging, production)
26
- - **Dynamic feature flags** and optional modules
27
- - **Type safety** throughout the entire configuration pipeline
28
- - **User-friendly error messages** when configuration goes wrong
29
-
30
- ### What You Need to Handle
31
-
32
- Without Cardigantime, building robust configuration management requires:
33
-
34
- 1. **Parse command-line arguments** - handled by Commander.js, but integration is manual
35
- 2. **Read configuration files** - YAML/JSON parsing with proper error handling
36
- 3. **Implement sophisticated merging logic** - CLI args should override file config, which should override defaults, with proper deep merging
37
- 4. **Validate complex nested structures** - ensure required fields exist, types are correct, and business rules are followed
38
- 5. **Handle edge cases gracefully** - missing files, malformed YAML, permission errors, invalid paths
39
- 6. **Provide actionable error messages** - users need to know exactly what's wrong and how to fix it
40
- 7. **Maintain type safety** - TypeScript support with proper IntelliSense throughout the entire pipeline
41
- 8. **Support advanced scenarios** - schema evolution, backward compatibility, configuration discovery
42
-
43
- ### The Manual Approach Pain Points
44
-
45
- Implementing this manually leads to common problems:
46
-
47
- ```typescript
48
- // Typical manual configuration merging - fragile and error-prone
49
- const config = {
50
- ...defaultConfig, // Defaults
51
- ...yamlConfig, // File config
52
- ...processCliArgs(args), // CLI overrides
53
- };
54
-
55
- // Problems:
56
- // ❌ Shallow merging loses nested structure
57
- // ❌ No validation until runtime failures
58
- // ❌ Poor error messages: "Cannot read property 'x' of undefined"
59
- // ❌ Type safety lost after merging
60
- // ❌ No protection against typos in config files
61
- // ❌ Manual path resolution and security checks
62
- ```
63
-
64
- ### How Cardigantime Solves This
65
-
66
- Cardigantime provides a complete, battle-tested solution:
67
-
68
- ```typescript
69
- // Cardigantime approach - robust and type-safe
70
- const cardigantime = create({
71
- defaults: { configDirectory: './config' },
72
- configShape: ComplexConfigSchema.shape, // Full type safety
73
- });
74
-
75
- const config = await cardigantime.read(args); // Smart merging
76
- await cardigantime.validate(config); // Comprehensive validation
77
-
78
- // Benefits:
79
- // ✅ Deep merging preserves nested structures
80
- // ✅ Schema validation with detailed error messages
81
- // ✅ Full TypeScript support with IntelliSense
82
- // ✅ Typo detection and helpful suggestions
83
- // ✅ Built-in security protections
84
- // ✅ Graceful error handling with actionable messages
85
- ```
86
-
87
- ### Real-World Example: Complex Configuration
88
-
89
- Here's the kind of complex configuration Cardigantime was designed to handle:
90
-
91
- ```typescript
92
- const ComplexConfigSchema = z.object({
93
- // Database configuration with multiple environments
94
- database: z.object({
95
- primary: z.object({
96
- host: z.string().default('localhost'),
97
- port: z.number().min(1).max(65535).default(5432),
98
- ssl: z.boolean().default(false),
99
- }),
100
- replicas: z.array(z.string().url()).default([]),
101
- maxConnections: z.number().positive().default(10),
102
- }),
103
-
104
- // Feature flags and optional modules
105
- features: z.record(z.boolean()).default({}),
106
-
107
- // API configuration with validation
108
- api: z.object({
109
- key: z.string().min(32, "API key must be at least 32 characters"),
110
- timeout: z.number().min(1000).max(30000).default(5000),
111
- retries: z.number().min(0).max(10).default(3),
112
- baseUrl: z.string().url(),
113
- }),
114
-
115
- // Logging configuration
116
- logging: z.object({
117
- level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
118
- outputs: z.array(z.enum(['console', 'file', 'syslog'])).default(['console']),
119
- rotation: z.object({
120
- maxSize: z.string().regex(/^\d+[KMG]B$/),
121
- maxFiles: z.number().positive().default(5),
122
- }).optional(),
123
- }),
124
- });
125
-
126
- // Users can now run:
127
- // ./myapp --api-timeout 10000 --features-analytics true --config-directory ./prod-config
128
- // And everything just works with full validation and type safety
129
- ```
130
-
131
- Cardigantime handles all of this complexity while providing excellent developer experience and robust error handling. **It was specifically created because existing solutions either lacked the sophistication needed for complex configuration scenarios or required too much boilerplate code to achieve proper integration between CLI arguments, configuration files, and defaults.**
25
+ Cardigantime provides a complete, battle-tested solution for all of this complexity.
132
26
 
133
27
  ## Installation
134
28
 
@@ -229,819 +123,42 @@ debug: true
229
123
  # Use different config directory
230
124
  ./myapp --config-directory /etc/myapp
231
125
 
232
- # Enable debug mode
233
- ./myapp --debug
234
- ```
235
-
236
- ### Advanced Usage Examples
237
-
238
- #### Basic Configuration with Path Resolution
126
+ # Generate initial configuration file
127
+ ./myapp --init-config
239
128
 
240
- ```typescript
241
- import { create } from '@theunwalked/cardigantime';
242
- import { z } from 'zod';
243
-
244
- const MyConfigSchema = z.object({
245
- apiKey: z.string().min(1),
246
- timeout: z.number().default(5000),
247
- debug: z.boolean().default(false),
248
- contextDirectories: z.array(z.string()).optional(),
249
- });
250
-
251
- const cardigantime = create({
252
- defaults: {
253
- configDirectory: './config',
254
- configFile: 'myapp.yaml',
255
- // Resolve relative paths in contextDirectories relative to config file location
256
- pathResolution: {
257
- pathFields: ['contextDirectories'],
258
- resolvePathArray: ['contextDirectories']
259
- }
260
- },
261
- configShape: MyConfigSchema.shape,
262
- });
129
+ # Analyze configuration with source tracking
130
+ ./myapp --check-config
263
131
  ```
264
132
 
265
- #### Hierarchical Configuration with Custom Array Overlap
266
-
267
- ```typescript
268
- const cardigantime = create({
269
- defaults: {
270
- configDirectory: '.myapp',
271
- configFile: 'config.yaml',
272
- fieldOverlaps: {
273
- 'features': 'append', // Accumulate features from all levels
274
- 'excludePatterns': 'prepend', // Higher precedence patterns come first
275
- 'api.endpoints': 'append', // Nested field configuration
276
- 'security.allowedOrigins': 'append' // Security settings accumulate
277
- }
278
- },
279
- configShape: MyConfigSchema.shape,
280
- features: ['config', 'hierarchical'], // Enable hierarchical discovery
281
- });
282
- ```
283
-
284
- This configuration enables powerful composition scenarios where:
285
- - **Features** from all configuration levels are combined (e.g., base features + project features + local features)
286
- - **Exclude patterns** are layered with local patterns taking precedence
287
- - **API endpoints** can be extended at each level
288
- - **Security settings** accumulate for maximum flexibility
289
-
290
- ## Core Concepts
291
-
292
- ### 1. Configuration Sources & Precedence
293
-
294
- Cardigantime merges configuration from multiple sources in this order (highest to lowest priority):
133
+ ## Key Features
295
134
 
135
+ ### Configuration Sources & Precedence
136
+ Merges configuration from multiple sources in order of precedence:
296
137
  1. **Command-line arguments** (highest priority)
297
138
  2. **Configuration file(s)** (medium priority)
298
139
  3. **Default values** (lowest priority)
299
140
 
300
- ```typescript
301
- // If you have this config file:
302
- // timeout: 5000
303
- // debug: false
304
-
305
- // And run: ./myapp --timeout 10000
306
-
307
- // The final config will be:
308
- // timeout: 10000 (from CLI, overrides file)
309
- // debug: false (from file)
310
- ```
311
-
312
- ### 2. Hierarchical Configuration Discovery
313
-
314
- Cardigantime supports hierarchical configuration discovery, similar to how tools like `.gitignore`, `.eslintrc`, or `package.json` work. When the `hierarchical` feature is enabled, Cardigantime will:
315
-
316
- 1. **Start from the specified config directory** (e.g., `./project/subdir/.kodrdriv`)
317
- 2. **Search up the directory tree** for additional config directories with the same name
318
- 3. **Merge configurations** with proper precedence (closer directories win)
319
- 4. **Apply CLI arguments** as the final override
320
-
321
- #### Example Directory Structure
322
-
323
- ```
324
- /home/user/projects/
325
- ├── .kodrdriv/
326
- │ └── config.yaml # Root-level config
327
- ├── myproject/
328
- │ ├── .kodrdriv/
329
- │ │ └── config.yaml # Project-level config
330
- │ └── submodule/
331
- │ ├── .kodrdriv/
332
- │ │ └── config.yaml # Submodule-level config
333
- │ └── my-script.js
334
- ```
335
-
336
- #### Hierarchical Discovery Behavior
337
-
338
- When running from `/home/user/projects/myproject/submodule/` with hierarchical discovery:
339
-
340
- 1. **Level 0 (Highest Priority)**: `/home/user/projects/myproject/submodule/.kodrdriv/config.yaml`
341
- 2. **Level 1**: `/home/user/projects/myproject/.kodrdriv/config.yaml`
342
- 3. **Level 2 (Lowest Priority)**: `/home/user/projects/.kodrdriv/config.yaml`
343
-
344
- Configurations are deep-merged, with closer directories taking precedence:
345
-
346
- ```yaml
347
- # /home/user/projects/.kodrdriv/config.yaml (Level 2)
348
- database:
349
- host: localhost
350
- port: 5432
351
- ssl: false
352
- logging:
353
- level: info
354
- features:
355
- - auth
356
- - basic-logging
357
-
358
- # /home/user/projects/myproject/.kodrdriv/config.yaml (Level 1)
359
- database:
360
- port: 5433
361
- ssl: true
362
- api:
363
- timeout: 5000
364
- features:
365
- - advanced-logging
366
- - metrics
367
-
368
- # /home/user/projects/myproject/submodule/.kodrdriv/config.yaml (Level 0)
369
- database:
370
- host: dev.example.com
371
- logging:
372
- level: debug
373
- features:
374
- - debug-mode
375
-
376
- # Final merged configuration (with default array behavior):
377
- database:
378
- host: dev.example.com # From Level 0 (highest precedence)
379
- port: 5433 # From Level 1
380
- ssl: true # From Level 1
381
- api:
382
- timeout: 5000 # From Level 1
383
- logging:
384
- level: debug # From Level 0 (highest precedence)
385
- features:
386
- - debug-mode # From Level 0 (arrays override by default)
387
- ```
388
-
389
- #### Configurable Array Overlap Behavior
390
-
391
- By default, arrays in hierarchical configurations follow the **override** behavior - arrays from higher precedence levels completely replace arrays from lower precedence levels. However, you can configure custom overlap behavior for array fields:
392
-
393
- ```typescript
394
- const cardigantime = create({
395
- defaults: {
396
- configDirectory: '.kodrdriv',
397
- configFile: 'config.yaml',
398
- fieldOverlaps: {
399
- 'features': 'append', // Combine features by appending
400
- 'excludePatterns': 'prepend', // Combine exclude patterns by prepending
401
- 'middlewares': 'override' // Override middlewares (default behavior)
402
- }
403
- },
404
- configShape: MyConfigSchema.shape,
405
- features: ['config', 'hierarchical'],
406
- });
407
- ```
408
-
409
- **Available Overlap Modes:**
410
-
411
- - **`override`** (default): Higher precedence arrays completely replace lower precedence arrays
412
- - **`append`**: Higher precedence array elements are appended to lower precedence arrays
413
- - **`prepend`**: Higher precedence array elements are prepended to lower precedence arrays
414
-
415
- **Example with Custom Array Overlap:**
416
-
417
- ```yaml
418
- # /home/user/projects/.kodrdriv/config.yaml (Level 2)
419
- features: ['auth', 'basic-logging']
420
- excludePatterns: ['*.tmp', '*.cache']
421
-
422
- # /home/user/projects/myproject/.kodrdriv/config.yaml (Level 1)
423
- features: ['advanced-logging', 'metrics']
424
- excludePatterns: ['*.log']
425
-
426
- # /home/user/projects/myproject/submodule/.kodrdriv/config.yaml (Level 0)
427
- features: ['debug-mode']
428
- excludePatterns: ['*.debug']
429
- ```
430
-
431
- With the configuration above (`features: 'append'`, `excludePatterns: 'prepend'`):
432
-
433
- ```yaml
434
- # Final merged configuration:
435
- features:
436
- - auth # From Level 2
437
- - basic-logging # From Level 2
438
- - advanced-logging # From Level 1
439
- - metrics # From Level 1
440
- - debug-mode # From Level 0 (appended)
441
- excludePatterns:
442
- - "*.debug" # From Level 0 (prepended first)
443
- - "*.log" # From Level 1 (prepended second)
444
- - "*.tmp" # From Level 2
445
- - "*.cache" # From Level 2
446
- ```
447
-
448
- **Nested Field Paths:**
449
-
450
- You can configure overlap behavior for nested array fields using dot notation:
451
-
452
- ```typescript
453
- fieldOverlaps: {
454
- 'api.endpoints': 'append',
455
- 'database.migrations': 'prepend',
456
- 'config.features.experimental': 'override'
457
- }
458
- ```
459
-
460
- #### Enabling Hierarchical Discovery
461
-
462
- ```typescript
463
- const cardigantime = create({
464
- defaults: {
465
- configDirectory: '.kodrdriv',
466
- configFile: 'config.yaml'
467
- },
468
- configShape: MyConfigSchema.shape,
469
- features: ['config', 'hierarchical'], // Enable hierarchical discovery
470
- });
471
- ```
472
-
473
- #### Hierarchical Discovery Options
474
-
475
- The hierarchical discovery has several built-in protections and features:
476
-
477
- - **Maximum traversal depth**: Prevents infinite loops (default: 10 levels)
478
- - **Symlink protection**: Tracks visited paths to prevent circular references
479
- - **Graceful fallback**: Falls back to single-directory mode if discovery fails
480
- - **Error tolerance**: Continues discovery even if some directories are unreadable
481
- - **Root detection**: Automatically stops at filesystem root
482
-
483
- #### Use Cases for Hierarchical Configuration
484
-
485
- 1. **Monorepos**: Share common configuration across multiple packages
486
- 2. **Project inheritance**: Override team/organization defaults for specific projects
487
- 3. **Environment layering**: Different configs for development/staging/production
488
- 4. **Tool configuration**: Similar to how ESLint or Prettier find configs up the tree
489
- 5. **Multi-tenant applications**: Tenant-specific overrides of global settings
490
-
491
- ### 3. Schema Validation
492
-
493
- All configuration is validated against your Zod schema:
494
-
495
- ```typescript
496
- const ConfigSchema = z.object({
497
- port: z.number().min(1).max(65535),
498
- host: z.string().ip().or(z.literal('localhost')),
499
- database: z.object({
500
- url: z.string().url(),
501
- maxConnections: z.number().positive().default(10),
502
- }),
503
- features: z.array(z.enum(['auth', 'analytics', 'logging'])).default([]),
504
- });
505
-
506
- const cardigantime = create({
507
- defaults: { configDirectory: './config' },
508
- configShape: ConfigSchema.shape,
509
- });
510
- ```
511
-
512
- ### 4. Type Safety
513
-
514
- Cardigantime provides full TypeScript support:
515
-
516
- ```typescript
517
- // The config object is fully typed
518
- const config = await cardigantime.read(args);
519
- // config.database.maxConnections is number
520
- // config.features is ('auth' | 'analytics' | 'logging')[]
521
- // config.port is number
522
-
523
- // IntelliSense works everywhere
524
- if (config.features.includes('auth')) {
525
- // Setup authentication
526
- }
527
- ```
528
-
529
- ### 5. Error Handling
530
-
531
- Cardigantime provides detailed error messages for common issues:
532
-
533
- ```typescript
534
- try {
535
- await cardigantime.validate(config);
536
- } catch (error) {
537
- // Detailed validation errors:
538
- // "Configuration validation failed: port must be between 1 and 65535"
539
- // "Unknown configuration keys found: typoKey. Allowed keys are: port, host, database"
540
- // "Config directory does not exist and is required: /nonexistent/path"
541
- }
542
- ```
543
-
544
- ## API Reference
545
-
546
- ### `create(options)`
547
-
548
- Creates a new Cardigantime instance.
549
-
550
- **Parameters:**
551
- - `options.defaults` (required): Default configuration options
552
- - `configDirectory` (required): Directory to look for config files
553
- - `configFile` (optional): Config filename, defaults to `'config.yaml'`
554
- - `isRequired` (optional): Whether config directory must exist, defaults to `false`
555
- - `encoding` (optional): File encoding, defaults to `'utf8'`
556
- - `options.configShape` (required): Zod schema shape for validation
557
- - `options.features` (optional): Array of features to enable, defaults to `['config']`
558
- - `options.logger` (optional): Custom logger implementation
559
-
560
- **Returns:** `Cardigantime` instance
561
-
562
- ### `cardigantime.configure(command)`
563
-
564
- Adds Cardigantime's CLI options to a Commander.js command.
565
-
566
- **Parameters:**
567
- - `command`: Commander.js Command instance
568
-
569
- **Returns:** Promise<Command> - The modified command
570
-
571
- **Added Options:**
572
- - `-c, --config-directory <path>`: Override config directory
573
-
574
- ### `cardigantime.read(args)`
575
-
576
- Reads and merges configuration from all sources.
577
-
578
- **Parameters:**
579
- - `args`: Parsed command-line arguments object
580
-
581
- **Returns:** Promise<Config> - Merged and typed configuration object
582
-
583
- ### `cardigantime.validate(config)`
584
-
585
- Validates configuration against the schema.
586
-
587
- **Parameters:**
588
- - `config`: Configuration object to validate
589
-
590
- **Returns:** Promise<void> - Throws on validation failure
591
-
592
- ### `cardigantime.setLogger(logger)`
593
-
594
- Sets a custom logger for debugging and error reporting.
595
-
596
- **Parameters:**
597
- - `logger`: Logger implementing the Logger interface
598
-
599
- ## Advanced Usage
600
-
601
141
  ### Hierarchical Configuration Discovery
142
+ Supports hierarchical configuration discovery, similar to how `.gitignore`, `.eslintrc`, or `package.json` work - searching up the directory tree for configuration directories.
602
143
 
603
- Here's a complete example of using hierarchical configuration discovery for a monorepo setup:
604
-
605
- ```typescript
606
- import { create } from '@theunwalked/cardigantime';
607
- import { z } from 'zod';
608
-
609
- // Define a comprehensive configuration schema
610
- const ProjectConfigSchema = z.object({
611
- projectName: z.string(),
612
- environment: z.enum(['development', 'staging', 'production']).default('development'),
613
- database: z.object({
614
- host: z.string().default('localhost'),
615
- port: z.number().default(5432),
616
- ssl: z.boolean().default(false),
617
- maxConnections: z.number().default(10),
618
- }),
619
- api: z.object({
620
- baseUrl: z.string().url(),
621
- timeout: z.number().default(5000),
622
- retries: z.number().default(3),
623
- }),
624
- features: z.record(z.boolean()).default({}),
625
- logging: z.object({
626
- level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
627
- outputs: z.array(z.string()).default(['console']),
628
- }),
629
- });
630
-
631
- // Enable hierarchical discovery
632
- const cardigantime = create({
633
- defaults: {
634
- configDirectory: '.myapp',
635
- configFile: 'config.yaml',
636
- },
637
- configShape: ProjectConfigSchema.shape,
638
- features: ['config', 'hierarchical'], // Enable hierarchical discovery
639
- });
640
-
641
- // Usage in a CLI tool
642
- async function setupProject() {
643
- try {
644
- const config = await cardigantime.read(process.argv);
645
- await cardigantime.validate(config);
646
-
647
- console.log(`Setting up ${config.projectName} in ${config.environment} mode`);
648
- console.log(`Database: ${config.database.host}:${config.database.port}`);
649
- console.log(`API: ${config.api.baseUrl}`);
650
-
651
- return config;
652
- } catch (error) {
653
- console.error('Configuration error:', error.message);
654
- process.exit(1);
655
- }
656
- }
657
- ```
658
-
659
- **Directory Structure:**
660
- ```
661
- /workspace/
662
- ├── .myapp/
663
- │ └── config.yaml # Global defaults
664
- ├── team-frontend/
665
- │ ├── .myapp/
666
- │ │ └── config.yaml # Team-specific settings
667
- │ ├── app1/
668
- │ │ ├── .myapp/
669
- │ │ │ └── config.yaml # App-specific overrides
670
- │ │ └── package.json
671
- │ └── app2/
672
- │ └── package.json # Uses team + global config
673
- ```
674
-
675
- **Configuration Files:**
676
- ```yaml
677
- # /workspace/.myapp/config.yaml (Global)
678
- database:
679
- host: prod.db.company.com
680
- ssl: true
681
- api:
682
- baseUrl: https://api.company.com
683
- logging:
684
- level: warn
685
- outputs: [console, file]
686
-
687
- # /workspace/team-frontend/.myapp/config.yaml (Team)
688
- database:
689
- host: team-frontend.db.company.com
690
- api:
691
- timeout: 3000
692
- features:
693
- analytics: true
694
- darkMode: true
695
-
696
- # /workspace/team-frontend/app1/.myapp/config.yaml (App)
697
- projectName: frontend-app1
698
- environment: development
699
- database:
700
- host: localhost # Override for local development
701
- logging:
702
- level: debug
703
- ```
704
-
705
- When running from `/workspace/team-frontend/app1/`, the final merged configuration will be:
706
-
707
- ```yaml
708
- projectName: frontend-app1 # From app level
709
- environment: development # From app level
710
- database:
711
- host: localhost # From app level (highest precedence)
712
- ssl: true # From global level
713
- api:
714
- baseUrl: https://api.company.com # From global level
715
- timeout: 3000 # From team level
716
- features:
717
- analytics: true # From team level
718
- darkMode: true # From team level
719
- logging:
720
- level: debug # From app level (highest precedence)
721
- outputs: [console, file] # From global level
722
- ```
723
-
724
- ### Custom Logger
725
-
726
- ```typescript
727
- import winston from 'winston';
728
-
729
- const logger = winston.createLogger({
730
- level: 'debug',
731
- format: winston.format.json(),
732
- transports: [
733
- new winston.transports.File({ filename: 'app.log' }),
734
- new winston.transports.Console(),
735
- ],
736
- });
737
-
738
- const cardigantime = create({
739
- defaults: { configDirectory: './config' },
740
- configShape: MyConfigSchema.shape,
741
- logger, // Use Winston for logging
742
- });
743
- ```
744
-
745
- ### Complex Configuration Schema
746
-
747
- ```typescript
748
- const DatabaseConfig = z.object({
749
- host: z.string(),
750
- port: z.number().min(1).max(65535),
751
- username: z.string(),
752
- password: z.string(),
753
- ssl: z.boolean().default(false),
754
- });
755
-
756
- const AppConfigSchema = z.object({
757
- app: z.object({
758
- name: z.string(),
759
- version: z.string(),
760
- environment: z.enum(['development', 'staging', 'production']),
761
- }),
762
- database: DatabaseConfig,
763
- redis: z.object({
764
- url: z.string().url(),
765
- ttl: z.number().positive().default(3600),
766
- }),
767
- features: z.record(z.boolean()).default({}), // Dynamic feature flags
768
- logging: z.object({
769
- level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
770
- file: z.string().optional(),
771
- }),
772
- });
773
- ```
774
-
775
- ### Environment-Specific Configuration
776
-
777
- ```typescript
778
- // Use different config directories for different environments
779
- const environment = process.env.NODE_ENV || 'development';
780
-
781
- const cardigantime = create({
782
- defaults: {
783
- configDirectory: `./config/${environment}`,
784
- configFile: 'app.yaml',
785
- },
786
- configShape: AppConfigSchema.shape,
787
- });
788
- ```
789
-
790
- ### Configuration File Discovery
791
-
792
- ```typescript
793
- // Cardigantime will look for config files in this order:
794
- // 1. CLI argument: --config-directory /path/to/config
795
- // 2. Default directory: ./config
796
- // 3. If not found and isRequired: false, continues with empty config
797
- // 4. If not found and isRequired: true, throws error
798
- ```
799
-
800
- ## Error Handling
801
-
802
- Cardigantime provides structured error types that allow you to handle different failure scenarios programmatically. All custom errors extend the standard JavaScript `Error` class and can be imported from the main package.
803
-
804
- ### Error Types
805
-
806
- ```typescript
807
- import {
808
- ConfigurationError,
809
- FileSystemError,
810
- ArgumentError
811
- } from '@theunwalked/cardigantime';
812
- ```
813
-
814
- #### ConfigurationError
815
-
816
- Thrown when configuration validation fails, contains extra keys, or schema issues occur.
817
-
818
- **Properties:**
819
- - `errorType`: `'validation' | 'schema' | 'extra_keys'`
820
- - `details`: Additional error context (e.g., Zod error details, extra keys info)
821
- - `configPath`: Path to the configuration file (when applicable)
822
-
823
- #### FileSystemError
824
-
825
- Thrown when file system operations fail (directory access, file reading, etc.).
826
-
827
- **Properties:**
828
- - `errorType`: `'not_found' | 'not_readable' | 'not_writable' | 'creation_failed' | 'operation_failed'`
829
- - `path`: The file/directory path that caused the error
830
- - `operation`: The operation that failed
831
- - `originalError`: The underlying error (when applicable)
832
-
833
- #### ArgumentError
834
-
835
- Thrown when CLI arguments or function parameters are invalid.
836
-
837
- **Properties:**
838
- - `argument`: The name of the invalid argument
839
-
840
- ### Error Handling Examples
841
-
842
- #### Basic Error Handling
843
-
844
- ```typescript
845
- import { create, ConfigurationError, FileSystemError, ArgumentError } from '@theunwalked/cardigantime';
846
-
847
- async function setupApp() {
848
- const cardigantime = create({
849
- defaults: { configDirectory: './config' },
850
- configShape: MyConfigSchema.shape,
851
- });
852
-
853
- try {
854
- const config = await cardigantime.read(args);
855
- await cardigantime.validate(config);
856
-
857
- // Your app logic here
858
- await startApp(config);
859
-
860
- } catch (error) {
861
- if (error instanceof ConfigurationError) {
862
- handleConfigError(error);
863
- } else if (error instanceof FileSystemError) {
864
- handleFileSystemError(error);
865
- } else if (error instanceof ArgumentError) {
866
- handleArgumentError(error);
867
- } else {
868
- console.error('Unexpected error:', error.message);
869
- process.exit(1);
870
- }
871
- }
872
- }
873
- ```
874
-
875
- #### Detailed Configuration Error Handling
876
-
877
- ```typescript
878
- function handleConfigError(error: ConfigurationError) {
879
- switch (error.errorType) {
880
- case 'validation':
881
- console.error('❌ Configuration validation failed');
882
- console.error('Details:', JSON.stringify(error.details, null, 2));
883
- console.error('Please check your configuration values against the schema.');
884
- break;
885
-
886
- case 'extra_keys':
887
- console.error('❌ Unknown configuration keys found');
888
- console.error('Extra keys:', error.details.extraKeys.join(', '));
889
- console.error('Allowed keys:', error.details.allowedKeys.join(', '));
890
- console.error('Please remove the unknown keys or update your schema.');
891
- break;
892
-
893
- case 'schema':
894
- console.error('❌ Configuration schema is invalid');
895
- console.error('Details:', error.details);
896
- break;
897
- }
898
-
899
- if (error.configPath) {
900
- console.error(`Configuration file: ${error.configPath}`);
901
- }
902
-
903
- process.exit(1);
904
- }
905
- ```
906
-
907
- #### File System Error Handling
908
-
909
- ```typescript
910
- function handleFileSystemError(error: FileSystemError) {
911
- switch (error.errorType) {
912
- case 'not_found':
913
- if (error.operation === 'directory_access') {
914
- console.error(`❌ Configuration directory not found: ${error.path}`);
915
- console.error('Solutions:');
916
- console.error(' 1. Create the directory: mkdir -p ' + error.path);
917
- console.error(' 2. Use a different directory with --config-directory');
918
- console.error(' 3. Set isRequired: false in your options');
919
- } else {
920
- console.error(`❌ Configuration file not found: ${error.path}`);
921
- console.error('Create the configuration file or check the path.');
922
- }
923
- break;
924
-
925
- case 'not_readable':
926
- console.error(`❌ Cannot read ${error.path}`);
927
- console.error('Check file/directory permissions:');
928
- console.error(` chmod +r ${error.path}`);
929
- break;
930
-
931
- case 'creation_failed':
932
- console.error(`❌ Failed to create directory: ${error.path}`);
933
- console.error('Original error:', error.originalError?.message);
934
- console.error('Check parent directory permissions.');
935
- break;
936
-
937
- case 'operation_failed':
938
- console.error(`❌ File operation failed: ${error.operation}`);
939
- console.error('Path:', error.path);
940
- console.error('Error:', error.originalError?.message);
941
- break;
942
- }
943
-
944
- process.exit(1);
945
- }
946
- ```
947
-
948
- #### Argument Error Handling
949
-
950
- ```typescript
951
- function handleArgumentError(error: ArgumentError) {
952
- console.error(`❌ Invalid argument: ${error.argument}`);
953
- console.error(`Error: ${error.message}`);
954
- console.error('Please check your command line arguments or function parameters.');
955
- process.exit(1);
956
- }
957
- ```
958
-
959
- #### Graceful Degradation
960
-
961
- ```typescript
962
- async function setupAppWithFallbacks() {
963
- const cardigantime = create({
964
- defaults: { configDirectory: './config' },
965
- configShape: MyConfigSchema.shape,
966
- });
967
-
968
- try {
969
- const config = await cardigantime.read(args);
970
- await cardigantime.validate(config);
971
- return config;
972
-
973
- } catch (error) {
974
- if (error instanceof FileSystemError && error.errorType === 'not_found') {
975
- console.warn('⚠️ Configuration not found, using defaults');
976
- return getDefaultConfig();
977
- }
978
-
979
- if (error instanceof ConfigurationError && error.errorType === 'extra_keys') {
980
- console.warn('⚠️ Unknown config keys found, continuing with valid keys only');
981
- // Filter out extra keys and retry
982
- const cleanConfig = removeExtraKeys(config, error.details.allowedKeys);
983
- await cardigantime.validate(cleanConfig);
984
- return cleanConfig;
985
- }
986
-
987
- // Re-throw other errors
988
- throw error;
989
- }
990
- }
991
- ```
992
-
993
- ### Error Messages and Troubleshooting
144
+ ### Type Safety & Validation
145
+ Full TypeScript support with Zod schema validation for robust, type-safe configuration management.
994
146
 
995
- #### Common Configuration Errors
996
-
997
- **Schema validation failed:**
998
- ```typescript
999
- // Error type: ConfigurationError with errorType: 'validation'
1000
- {
1001
- "port": {
1002
- "_errors": ["Number must be greater than or equal to 1"]
1003
- }
1004
- }
1005
- ```
1006
- *Solution:* Fix the configuration values to match your schema requirements.
147
+ ### Error Handling
148
+ Comprehensive error handling with detailed, actionable error messages to help users fix configuration issues quickly.
1007
149
 
1008
- **Unknown configuration keys:**
1009
- ```typescript
1010
- // Error type: ConfigurationError with errorType: 'extra_keys'
1011
- // error.details.extraKeys: ['databse']
1012
- // error.details.allowedKeys: ['database', 'port', 'host']
1013
- ```
1014
- *Solution:* Fix typos in your configuration file or update your schema.
150
+ ## Documentation
1015
151
 
1016
- #### Common File System Errors
152
+ 📚 **[Complete Documentation](https://semicolonambulance.github.io/cardigantime/)** - Full documentation site
1017
153
 
1018
- **Configuration directory not found:**
1019
- ```typescript
1020
- // Error type: FileSystemError with errorType: 'not_found'
1021
- // error.path: '/etc/myapp'
1022
- // error.operation: 'directory_access'
1023
- ```
1024
- *Solutions:*
1025
- - Create the directory: `mkdir -p /etc/myapp`
1026
- - Use a different directory: `--config-directory ./config`
1027
- - Make it optional: `isRequired: false`
1028
-
1029
- **Directory not readable:**
1030
- ```typescript
1031
- // Error type: FileSystemError with errorType: 'not_readable'
1032
- // error.path: '/etc/restricted'
1033
- // error.operation: 'directory_read'
1034
- ```
1035
- *Solution:* Check file permissions: `chmod +r /etc/restricted`
1036
-
1037
- #### Common Argument Errors
1038
-
1039
- **Invalid config directory argument:**
1040
- ```typescript
1041
- // Error type: ArgumentError with argument: 'config-directory'
1042
- // Triggered by: --config-directory ""
1043
- ```
1044
- *Solution:* Provide a valid directory path: `--config-directory ./config`
154
+ **Quick Links:**
155
+ - [Getting Started Guide](https://semicolonambulance.github.io/cardigantime/#getting-started) - Detailed setup and basic concepts
156
+ - [Core Concepts](https://semicolonambulance.github.io/cardigantime/#core-concepts) - Configuration sources, hierarchical discovery
157
+ - [API Reference](https://semicolonambulance.github.io/cardigantime/#api-reference) - Complete API documentation
158
+ - [Configuration Options](https://semicolonambulance.github.io/cardigantime/#configuration-options) - All available options
159
+ - [Debugging & Analysis](https://semicolonambulance.github.io/cardigantime/#debugging-analysis) - Tools for analyzing config
160
+ - [Advanced Usage](https://semicolonambulance.github.io/cardigantime/#advanced-usage) - Complex examples and scenarios
161
+ - [Error Handling](https://semicolonambulance.github.io/cardigantime/#error-handling) - Comprehensive error handling guide
1045
162
 
1046
163
  ## Contributing
1047
164